Monday, July 5, 2010

Resolved the Wrap text issue in FPDF table cell





We are using open source php class FPDF for creating pdf file for exporting reports such as employees timesheet details in tabular format.


The below sample code provided by fpdf.org is working fine if the the table cell is having content with small length. Otherwise the content of one cell will overlap with other cell content.

function FancyTable($header,$data)
{
//Colors, line width and bold font
$this->SetFillColor(255,0,0);
$this->SetTextColor(255);
$this->SetDrawColor(128,0,0);
$this->SetLineWidth(.3);
$this->SetFont('','B');
//Header
$w=array(40,35,40,45);
for($i=0;$i<count($header);$i++)
$this->Cell($w[$i],7,$header[$i],1,0,'C',true);
$this->Ln();
//Color and font restoration
$this->SetFillColor(224,235,255);
$this->SetTextColor(0);
$this->SetFont('');
//Data
$fill=false;
foreach($data as $row)
{
$this->Cell($w[0],6,$row[0],'LR',0,'L',$fill);
$this->Cell($w[1],6,$row[1],'LR',0,'L',$fill);
$this->Cell($w[2],6,number_format($row[2]),'LR',0,'R',$fill);
$this->Cell($w[3],6,number_format($row[3]),'LR',0,'R',$fill);
$this->Ln();
$fill=!$fill;
}
$this->Cell(array_sum($w),0,'','T');
}
}


By reading this article I came to know that we can use Multicell for wrapping the text either implicitly reaching at right border or explicitly by using '\n'.

But, still I faced some issues as our requirement is somewhat different from the sample code mentioned in this article.

I have tried different ways to make the height of the multicell uniform for all columns in the table.

But it didn't help. I came to know that we can specify height only for the specific cell but not for the set of multiple cells used for creating one row of a table.

So, the border were not displaying correctly even when the text wrap was working correctly when using MultiCell.

So, I decided to use combination of both Cell and MultiCell.

i-e Use Cell for designing the table structure without putting any actual data. And, use MultiCell to put data with wrap feature.

In other words, combining both Cell and MultiCell to get benefit of both good alignment and word wrap feature.

First I called Cell without specifying any data for creating a table structure with borders.

Than Called SetXY for bringing back the cursor to the original position. And then called MultiCell for putting the data.


The code will look like this.

$yH=30; //height of the row
$this->SetXY($x, $y);
$this->Cell($w[0], $yH, "", 'LRB',0,'',$fill);
$this->SetXY($x, $y);
$this->MultiCell($w[0],6,$row[0],0,'L');

Find below the final code which can be used to create tabular pdf reports with proper alignment and with word wrap feature.


function FancyTable($header,$data)
{
//Colors, line width and bold font
$this->SetFillColor(255,0,0);
$this->SetTextColor(255);
$this->SetDrawColor(128,0,0);
$this->SetLineWidth(.3);
$this->SetFont('','B');
//Header
$w=array(60,60,20,30,15,15);
for($i=0;$i<count($header);$i++)
$this->Cell($w[$i],7,$header[$i],1,0,'C',true);
$this->Ln();
//Color and font restoration
$this->SetFillColor(224,235,255);
$this->SetTextColor(0);
$this->SetFont('');
//Data
$fill=false;

$i = 0;


$x0=$x = $this->GetX();
$y = $this->GetY();
foreach($data as $row)
{

for ($i=0; $i<6; $i++) //Avoid very lengthy texts { $row[$i]=substr($row[$i],0,160); } $yH=30; //height of the row $this->SetXY($x, $y); $this->Cell($w[0], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x, $y); $this->MultiCell($w[0],6,$row[0],0,'L'); $this->SetXY($x + $w[0], $y); $this->Cell($w[1], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x + $w[0], $y); $this->MultiCell($w[1],6,$row[1],0,'L'); $x =$x+$w[0]; $this->SetXY($x + $w[1], $y); $this->Cell($w[2], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x + $w[1], $y); $this->MultiCell($w[2],6,$row[2],0,'L'); $x =$x+$w[1]; $this->SetXY($x + $w[2], $y); $this->Cell($w[3], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x + $w[2], $y); $this->MultiCell($w[3],6,$row[3],0,'L'); $x =$x+$w[2]; $this->SetXY($x + $w[3], $y); $this->Cell($w[4], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x + $w[3], $y); $this->MultiCell($w[4],6,$row[4],0,'L'); $x =$x+$w[3]; $this->SetXY($x + $w[4],$y); $this->Cell($w[5], $yH, "", 'LRB',0,'',$fill); $this->SetXY($x + $w[4], $y); $this->MultiCell($w[5],6,$row[5],0,'L'); $y=$y+$yH; //move to next row $x=$x0; //start from firt column $fill=!$fill; } } }




Limitation of this code is, we need to specify a fixed height ($yH) for the table row in this code.

It is not possible to specify this height value if you don't have any clue about length of the dynamically generated text.

In this case we need to add a piece of code to set the $yH value dynamically.


i-e The code should measure the height of the cell for each column using GetY() method. i-e we need to find the difference by calling the GetY() before and after calling the MultiCell method.

Then we need to find the largest $yH among all columns. This largest value should be used as $yH when actually creating the table.

i-e We need to use the code two times, one time for finding the appropriate height and another time for creating table using the found height.

If you know any simple way to achieve all these things you can share it thro' the comments.

The above sample code will create issue if the pdf content grows more than one page.

In this case we need to call addPage() for every page of the content.

In our settings, each page hold 7 rows of data. So I have updated the code to call addPage() on every 7th row using modulo operator. And, we need to call at end of the all rows also in addition to call it at every 7th row.

Now the code will look like this.

$j=0;
$i=1;

foreach ($data as $row)
{
$pagedata[$j]=$row;

if (($i%7)==0 || ($i==count($data)))
{
$pdf->AddPage();
$pdf->FancyTable($header,$pagedata);
$j=0;
$pagedata="";


}
$i++;
$j++;

Updates: Read my new post to know the updated code which can handle the first page properly.

More Articles...
You can bookmark this blog for further reading, or you can subscribe to our blog feed.

3 comments:

Marco Almodova said...

Actually you can measure the height of the cells by trying to find the highest height when creating the cells of the row and after calling a function to draw the borders, using as height to create the bordes the difference between the highest height found and an Y saved before drawning the data.

Jose said...

I don't know if Marco's suggestion would work, but the way I implemented the flexible height was by using 3 for loops. One draws MultiCells to calculate the max height. Previously you store the original x and y in some variables. Another for loop draws Cells using the max height, starting from the original x and y (this effectively overwrites the first MultiCells). Finally you use another for loop to write the MultiCells once again and at the same x and y co-ordinates. It's very inefficient but it is the only way I could figure out to do it. Anyway, FPDF is a very primitive library and it's not maintained anymore; so, not much can be expected from it.

Anonymous said...

good idea, but I made it even better :D
http://pastebin.com/kGLYEEYg

btw: it is copy pasted from what I needed to do, so it actually is not the same table :D

Search This Blog