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
			$pdf->Ln();
		}
		$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->Ln();
}
 
$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.

 

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

  1. First I would like to apologize if I revive the post. I’m trying to make a zebra color using the FPDF’s FancyTable $ fill, however I can’t figure out where I can locate the fill. None of the combinations I use makes this task easier for me. Thank you!

  2. Utilizzando la funzione getNumLines che Dan ha suggerito e adattandola alle mie esigienze ho fatto in modo che le multicell di un documento pdf generato dall’estrazione di dati da Mysql adattasse l’altezza della cella a quella con maggior numero di righe di un record di una determinata riga.
    Di seguito il codice commentato.

    codice:
    // qui le varie impostazioni del pdf
    $pdf = new PDF(‘L’, ‘mm’, ‘A4’, true, ‘UTF-8’, false);
    $pdf->SetMargins(5, 50, 5);
    $pdf->SetAutoPageBreak(true, 8);
    $pdf->AddPage();

    // connessione al db
    $conn_db = mysqli_connect($host, $username, $password, $database);

    mysqli_set_charset($conn_db, “utf8”);

    $result=mysqli_query($conn_db, “SELECT * FROM …..

    $posizione_y=50; // posizione di start della prima multicell da stampare

    while($row = mysqli_fetch_array($result)) { //eseguo il ciclo while per estrarre i dati interessati dal db

    $id=$row[‘Id’];
    $data=$row[‘Data’];
    $ora=$row[‘Ora’];
    $descrizione=$row[‘Descrizione’];
    $ricambi=$row[‘Ricambi’];
    $note=$row[‘Note’];

    $h_cell_des = $pdf->getNumLines($descrizione, 90)*4; // recupero valore altezza cella descrizione
    $h_cell_ricambi = $pdf->getNumLines($ricambi, 90)*4; // recupero valore altezza cella ricambi
    $h_cell_note = $pdf->getNumLines($note, 90)*4; // recupero valore altezza cella note
    // il 90 è la larghezza impostata della multicell
    // il 4 è un numero che varia in base alla dimensione del carattere

    $array = array($h_cell_des, $h_cell_ricambi, $h_cell_note); // inserisco le 3 o più variabili in un array

    rsort($array); // ordino l’array dal maggiore al minore

    $h_cell= $array[0]; // recupero il valore di quello maggiore

    if($h_cell=200){
    $posizione_y=50;
    $pdf->AddPage();
    }

    // queste righe non hanno bisogno di commenti (notare solo $h_cell) come impostazione altezza multicell
    $pdf->SetTextColor(0,0,0);
    $pdf->SetFont(‘helvetica’,”,8);
    $pdf->SetFillColor(255,255,255);
    $pdf->MultiCell(10,$h_cell,$id, 1,’C’, true, 0, ”, ”, true, 0, false, false, 0, ‘M’, true);
    $pdf->MultiCell(20,$h_cell,$data_it, 1, ‘C’, true, 0, ”, ”, true, 0, false, false, 0, ‘M’, true);
    $pdf->MultiCell(90,$H_cell,$descrizione, 1,’L’, true, 0, ”, ”, true, 0, false, false, 0, ‘M’, true);
    $pdf->MultiCell(90,$H_cell,$ricambi, 1,’L’, true, 0, ”, ”, true, 0, false, false, 0, ‘M’, true);
    $pdf->MultiCell(90,$H_cell,$note, 1, ‘L’, true, 1, ”, ”, true, 0, false, false, 0, ‘M’, true);

    $posizione_y=$posizione_y+$h_cell; // ricalcolo il valore di posizione_y e aggiungo il valore dell’altezza della multicell stampata
    }

    …..ecc.
    Con questo sistema tutta la riga stampata ha l’altezza della cella che occupa più spazio in altezza e in più nel caso di più pagine, il salto pagina avviene non appena una cella non riesce a stare dentro la misura impostata della pagina. Si noterà infatti che l’ultima cella stampata su ogni pagina potrà avere una distanza diversa dal bordo inferiore.

    Spero che il codice sia comprensibile.
    Ciao
    Federico

  3. 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.

  4. 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?

  5. 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 *

This site uses Akismet to reduce spam. Learn how your comment data is processed.