rospdf / pdf-php

Official R&OS PHP Pdf repository
http://pdf-php.sourceforge.net/
Other
136 stars 64 forks source link

ezTable fixed height #149

Closed pjaucamp closed 3 years ago

pjaucamp commented 3 years ago

I am working on a project to create an invoice, the invoice can have 1 or many line items and each line item can wrap over 1 or more lines depending on the text/description length. To make it more aesthetically pleasing I would like to set a fixed height for the table. Is this possible in some way. Previously I estimated the number of lines that will fit and then just filled the remainder of the table with empty lines, but this is a dirty solution and does not always work as some lines of text can wrap.

Manuzza commented 3 years ago

Hi,

I am not sure if there is a better way but this is how I would do it... This method assumes you are using fixed width columns, the full width of the page within the margins, and do NOT have row lines inside the table (just the border and vertical lines which is typical for an invoice). It also assumes invoices that will never have so many lines that it fills and exceeds your allowable space, which is a different problem... :-)

  1. Turn off ALL lines in the table layout.
  2. Work out where you want the table to start and end ($this->y position as $yTop and $yBottom).
  3. Move the virtual cursor to the top of the table. You may need to come down a bit if the content does not sit within the borders (eg, start slightly below $yTop).
  4. Display/render the table content (without lines and using fixed widths).
  5. Using the known $yTop and $yBottom positions for your table simply draw the table lines yourself over the top of the content - you have all the info you need. For example:
// draw the table border
$this->rectangle($this->ez["leftMargin"], $yBottom, ($this->ez->["pageWidth"] - $this->ez->["leftMargin"] - $this->ez->["rightMargin"]), ($yTop - $yBottom));

// then draw all of the vertical lines
$x = $this->ez["leftMargin"];
$x += $widthOfColumn1;
$this->line($x, $yTop, $x, $yBottom);
$x += $widthOfColumn2;
$this->line($x, $yTop, $x, $yBottom);
// repeat for all but the last column, which already has a line

You can also add an extra horizontal line under any headings at the top of the column, if you have them ($y position would be "$y1 - $heightOfTitleRow" which you will have to figure out for yourself).

Does that help?

pjaucamp commented 3 years ago

Thank you for your reply @Manuzza it works perfectly! Now the next problem is that the invoice can (but usually will not) contain so many lines so it spans over 2 or 3 pages. Any ideas? I am thinking of creating a dummy pdf object, output the table and check the new y-value to see if it still fits on the same page. If not then I have to reduce the number of lines untill it fits on one page and do the same to create the second page. (the second page will have different start and end positions as the header will be different and the totals must be on the last page only etc. so quite a bit to take into account...

Manuzza commented 3 years ago

There are a lot of complex and clever ways to do this but let me share the simplest.

Look in the manual regarding transactions. Transactions are your incredibly powerful best friend and they allow you to do LOTS of very VERY clever things in layout. They allow you to draw in the pdf, have a look at what happened and decide whether to keep it or roll it back so you can try again or do something else. You can use it to keep a block of layout together on one page, or to go back and change the font size to make text fit into a particular space. I have even used it to perform complex calculations so that a column layout in a pdf automatically balances (both columns on the last page end at the same place). Learn it and use it!

Your invoice table is now a fixed width so do this:

Draw each line of your invoice in a separate table. Each iteration uses a transaction. After adding a line to the invoice decide whether two keep it or roll back, start a new page and start over. Therefore the pseudo code will look like this:

$yTop = 1234; // whatever you are using for the start of the table
$yBottom = 56; // whatever you are using for the bottom of the table
DrawTheInvoiceBordersAndTitles($yTop, $yBottom);
foreach($InvoiceLines as $Line)
{
  $PageCount = sizeof($this->ezPages);
  $this->transaction("start");
  DrawTheInvoiceLineUsingATable($Line);
  // check to see we are on the same page and above the bottom of the invoice table
  if (($PageCount == sizeof($this->ezPages)) && ($this->y > $yBottom)) 
  {
    $this->transaction("commit");
  }
  else
  {
    $this->transaction("abort");
    $this->ezNewPage();
    $yTop = 1234; // change this if page 2+ uses a different number
    $yBottom = 56; // change this if page 2+ uses a different number
    DrawTheInvoiceBordersAndTitles($yTop, $yBottom);
    DrawTheInvoiceLineUsingATable($Line);
  }
}
pjaucamp commented 3 years ago

Thank you @Manuzza it works perfectly now!