How to calculate the height of a MultiCell/writeHTMLCell in TCPDF?
Asked Answered
L

8

18

I try to create a PDF with multiple pages and need to calculate the height of each individual element (MultiCell) in advance to prepare for a page break. According to the documentation there are a couple of functions out there like GetCharWidth/GetStringWidth to support me in doing it on my own, but besides a potential performance lost I probably will not do it the right anyway. Suggestions to achieve my goal in a more elegant way?

Reference: TCPDF

Loralorain answered 3/7, 2009 at 10:12 Comment(0)
M
30

I GOT it :D!!!!!

Create another pdf2 object

// pdf2 set x margin to pdf1's xmargin, but y margin to zero
// to make sure that pdf2 has identical settings, you can clone the object (after initializing the main pdf object)
$pdf2 = clone $pdf;
pdf2->addpage
pdf2->writeCell
$height = pdf2->getY()
pdf2->deletePage(pdf2->getPage())
pdf1->checkPageBreak($height);
pdf1->writeCell()

W00tness :D

Mock answered 21/12, 2009 at 22:38 Comment(5)
At first I was a bit sceptical about this answer, but it does work quite well. Actually better than any other method I've tried so far. Although you shouldn't forget to put $pdf2 on the same font as $pdf1Durango
Actually works quite well to be fair. If you're cloning then font should be the same anyway. If you're using this to dynamically generate tables don't forget that the widths are also importantStealth
This does not work for MultiCell if $ln=0, because then the cursor moves right and getY() does not give the bottom of the "highest" cell (if the text was too long to fit in it - thus different cell heights).Prudish
So you might have to use $ln=1 and reset y and X after each MultiCell.Prudish
I'm printing a complex table using writeHTMLCell and I never want to split the table. After futzing around with all the different solutions I could find and think of, I came back to this one. This double printing seems to be the only accurate and consistent solution. To make things easier, I created a function to print the entire table and I pass in the clone ($pdf2) the first call and then the original pfd object after doing break. The only change I made was that the clone's getY included the header. So I had to subtract that off to get the true height of the table I was printing.Sarco
F
24

This is an old question, but the current version (as of 7 Dec 2011) of TCPDF has a function called getStringHeight that allows you to calculate the resulting height of a string passed to MultiCell prior to actually calling MultiCell. Then this height can be used for various things, the calculation in the original question, and also for setting row height when making tables etc. Works great.

Just some info in case someone else stumbles across this question looking for a solution to this problem as I did.

Froemming answered 7/12, 2011 at 14:38 Comment(2)
". . . also for setting row height when making tables" ==> YES. I was a little flummoxed to find that Multicells don't automatically set row height to the height of the tallest cell. But using the maximum of the heights of the text in the row, obtained with this method, is an easy enough fix.Metamorphosis
Link is no longer valid, here is correct one: tcpdf.org/docs/srcdoc/tcpdf/class-TCPDF/#_getStringHeightYordan
E
10

While Carvell's answer is great, TCPDF mentions that getStringHeight returns the estimated height. Helpfully the documentation there provides a pretty comprehensive technique for getting the exact height which comes out as $height. As to why they don't use this themselves is a mystery...

// store current object
$pdf->startTransaction();
// store starting values
$start_y = $pdf->GetY();
$start_page = $pdf->getPage();
// call your printing functions with your parameters
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='',     $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// get the new Y
$end_y = $pdf->GetY();
$end_page = $pdf->getPage();
// calculate height
$height = 0;
if ($end_page == $start_page) {
    $height = $end_y - $start_y;
} else {
    for ($page=$start_page; $page <= $end_page; ++$page) {
        $pdf->setPage($page);
        if ($page == $start_page) {
            // first page
            $height = $pdf->h - $start_y - $pdf->bMargin;
        } elseif ($page == $end_page) {
            // last page
            $height = $end_y - $pdf->tMargin;
        } else {
            $height = $pdf->h - $pdf->tMargin - $pdf->bMargin;
        }
    }
}
// restore previous object
$pdf = $pdf->rollbackTransaction();
Eighteenmo answered 14/8, 2014 at 8:44 Comment(3)
This is a problem if $ln=0, because then $start_y = $end_y. I wish they would store the value of Y, before moving the cursor right.Prudish
So you might have to use $ln=1 and reset y and X after each MultiCell.Prudish
NOTE! In newest version some properties are protected and you have to changed the following: $pdf->h to $pdf->getPageHeight(), $pdf->bMargin to $pdf->getBreakMargin(), and $pdf->tMargin to $pdf->getMargins()['top']Mitsue
S
5

From my experience, it is nearly impossible to figure out the cell height in advance. It is much easier to use the page break handling functions of TCPDF that sort of tells you in advance if you're heading into a pagebreak. Here is example code:

$yy = $this->pdf->GetY();

$check_pagebreak = $this->pdf->checkPageBreak($height+$padding,$yy,false);

Change false to true to allow for an automatic page break, otherwise, you can handle the logic of the pagebreak, yourself, which is what I ended up doing.

Also, in case you may need this, here's another little tip: Consider using the transaction features to create your document in two passes. The first pass is used to figure out all the heights and cells, pagebreaks, etc. You can also store all your lineheights and lines per page in arrays. ON the second pass, create your document with all the correct information and no need for pagebreak logic (the second pass could be run from a seperate method, for the sake of keeping the code easier to read, and for your sanity).

Seducer answered 10/9, 2009 at 17:30 Comment(0)
P
3

Use TCPDF Example 20

  1. Calculating MultiCell heights can be a nightmare if the cells/columns end on different pages.

  2. Using transactions or additional pdf objects can make things very slow.

  3. Using functions such as getNumLines() and getStringHeight() to calculate the 'estimated' (see docs) height before the cells are printed do not always work correctly. Especially if the text ends just before or just after the right border of the cell - resulting in rows being printed on top of each other.

I prefer the technique used in Example 20 where the maximum Y value of the different pages are used to calculate the position of the new row.

The example prints only two columns, but I changed its main function to be able to print an array of columns. Obviously you could add more data to the array, such as each column's font, borders, etc.

public function MultiRow($columnsArray) {
    $page_start = $this->getPage();
    $y_start    = $this->GetY();

    $pageArray  = array();
    $yArray     = array();

    // traverse through array and print one column at a time.
    $columnCount = count($columnsArray);
    for($i=0; $i<$columnCount; $i++)
    {
        if($i+1 < $columnCount)
        {
            // Current column is not the last column in the row.
            // After printing, the pointer will be moved down to
            // the right-bottom of the column - from where the
            // next multiCell in the following loop will use it
            // via $this->GetX().
            $ln = 2;
        }
        else
        {
            // Current column is the last column in the row.
            // After printing, the pointer will be moved to new line.
            $ln = 1;
        }
        $this->MultiCell(30, 0, $columnsArray[$i], 1, 'L', 1, $ln,
            $this->GetX() ,$y_start, true, 0);

        $pageArray[$i]  = $this->getPage();
        $yArray[$i]     = $this->GetY();

        // Go to page where the row started - to print the
        // next column (if any).
        $this->setPage($page_start);
    }

    // Test if all columns ended on the same page
    $samePage = true;
    foreach ($pageArray as $val) {
       if($val != $pageArray['0']) 
       {
          $samePage = false;
          break;
       }
    }

    // Set the new page and row position by case
    if($samePage == true) 
    {
        // All columns ended on the same page.
        // Get the longest column.
        $newY = max($yArray);
    }
    else
    {
        // Some columns ended on different pages.
        // Get the array-keys (not the values) of all columns that
        // ended on the last page.
        $endPageKeys = array_keys($pageArray, max($pageArray));

        // Get the Y values of all columns that ended on the last page,
        // i.e. get the Y values of all columns with keys in $endPageKeys.
        $yValues = array();
        foreach($endPageKeys as $key)
        {
            $yValues[] = $yArray[$key];
        }

        // Get the largest Y value of all columns that ended on
        // the last page.
        $newY = max($yValues);
    }

    // Go to the last page and start at its largets Y value
    $this->setPage(max($pageArray));
    $this->SetXY($this->GetX(),$newY);
}
Prudish answered 22/10, 2015 at 10:3 Comment(0)
N
1

The post Revisited: Tcpdf – Variable Height Table Rows With MultiCell has a lot of useful information. This is a short extract:

getNumLines() ... 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
Nutmeg answered 18/8, 2009 at 14:28 Comment(1)
Both getNumLines() and getStringHeight() only give an 'estimated height' (see docs). These functions do not always work correctly if the text ends just before or just after the right border of the cell - resulting in rows being printed on top of each other. Rather use the technique in example 20.Prudish
H
0

I tried using Jay's answer and it worked for the intended purpose but for some reason caused my logo to not appear after the first page. I didn't want to do an in depth analysis, but had something to do with the cloning. I then tried the same approach, but using transactions. This produced hundreds of errors.

Then I came up with this rather simple solution using the same object.

/**
 * Gets an accurate measurement of a cell's rendered height.
 *
 * @param   float   $width     the width of the column to be rendered
 * @param   string  $contents  the contents to be rendered
 *
 * @return float
 */
private function getCellHeight(float $width, string $contents): float
{
    $view        = $this->view;
    $currentPage = $view->getPage();
    $currentX    = $view->GetX();
    $currentY    = $view->GetY();
    $view->AddPage();
    $x     = $view->GetX();
    $start = $view->GetY();
    $view->writeHTMLCell($width, 15, $x, $start, $contents, self::INSTANCE_BORDER, 1);
    $height = $view->GetY() - $start;
    $view->deletePage($view->getPage());
    $view->setPage($currentPage);
    $view->changePosition($currentX, $currentY);

    return $height;
}

As the writeHTMLCell function requires a $h, I use 15, but it can be anything you want, as can the $border value.
The $ln value needs to be set to 1, otherwise the y value resets before the GetY() can get it.
changePosition is my own wrapper for SetXY().

Hard answered 31/3, 2021 at 17:29 Comment(0)
H
0

As Rob Forrest's answer go I must add another $pdf->Ln() after $pdf->MulticCell() to get the $pdf->GetY() correct value

Hartle answered 18/4 at 8:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.