I have made good progress with producing pdf's with Zend PDF.
I am now integrating a web service into the application i am building. Everything has gone smoothly, but am now struggling to render a dynamically produced pdf that is to contain a pdf label returned via web service. Consider the following :
1.) An action to print the pdf is called.
2.) the pdf will contain n pages (n objects/items) of dynamic information about the item/shipment.
3.) each of the n shipments contains a shipment number, that when passed as a parameter to a specific method of the web-service, returns a pdfstring (which i can save as pdf).
4.) i have to resize and add each label to the bottom of the appropriate page.
so far, i have been able to use an already saved pdf, and have managed to load the pdfstring thats returned from the web service, and "merge" it into a final pdf. this works fine. but i would actually like to do the following:
1.) start a new pdf and add all other content to it.
2.) make the SOAP call and extract the returned Pdf string.
i have already done that, and now i would like to :
3.) resize the "label"
4.) rotate it 90 degrees
5.) add it to the pdf thats being built. (not merge with an already existing pdf).
so if i :
$returnedFromWebServicePDF = Zend_Pdf::parse($pdfLabel, 1);
I am able to save it on its own or even merge it (via cloning see here) but would like to resize, rotate and the add it to the pdf im building.
could someone guide me in the right direction as im pretty stumped. if someone could simply show me how to add this to a dynamic document, that would suffice as an answer to this question.
thanks in advance
EDIT
My efforts this far have been fruitless. i am now trying to change major parts of the applications processes, in order to already have stored the pdf, before this action is called. which means i could merge a static pdf into a dynamically generated one. will keep this problem updated.
Just to clarify, your goal is to download a PDF created by the service, load it back into Zend_PDF and make a change to content which exists in the pdf.
Zend_PDF is good at adding content to an existing pdf but doesn't have the ability to change content already on the page.
It does let you add new content which layers on-top of existing content.
If you don't have control over the service, you can redact the content your trying to change with a shape filled with the background color and add a new text block over the shape.
We use something similar to make personalized certificates.
Its tricky getting it in just the right spot.
I use a grid overlay on the pdf to get my bearings on the pdf.
/**
* Creates a grid overlay on a pdf. Useful for positioning text on a page.
* #param integer intPage The page number to operate on.
* #param integer intInterval. Lines on the page will be drawn at this interval. Value is in points.
* #return boolean TRUE for success.
*/
public function addGridLines($intPage = 0, $intInterval = 25)
{
$this->objPdf->pages[$intPage]->saveGS();
$pageWidth = $this->objPdf->pages[$intPage]->getWidth();
$pageHeight = $this->objPdf->pages[$intPage]->getHeight();
$this->objPdf->pages[$intPage]->setStyle($this->arrStyles['defaultLine']);
$this->objPdf->pages[$intPage]->setStyle($this->arrStyles['defaultText']);
//draw lines across the page.
for($a = 0; $a < $pageHeight; $a+= $intInterval){
$x = 0;
$y = $a;
$this->objPdf->pages[$intPage]->drawLine($x, $y, $pageWidth, $y);
$this->objPdf->pages[$intPage]->drawText($a, $x, $y);
}
//Draw lines up the page.
for($a = 0; $a < $pageHeight; $a+= $intInterval){
$x = $a;
$y = 0;
$this->objPdf->pages[$intPage]->drawLine($x, $y, $x, $pageHeight);
$this->objPdf->pages[$intPage]->drawText($a, $x, $y);
}
$this->objPdf->pages[$intPage]->restoreGS();
return TRUE;
}
This method is in another object with pdf related methods, but you get the idea.
The 'objPdf' is a Zend_PDF object I'm working with.
Related
I've been struggling with this for a while and feel helpless.
Prestashop uses tcpdf to generate invoices and delivery slips from HTML templates filled using Smarty. We are working on updating the invoice design and found tcpdf to be lacking in CSS support. After some research we settled for wkhtmltopdf as the right tool from converting the HTML/CSS templates to PDF.
The problem
The store has a feature for exporting multiple invoices into a single PDF. Using TCPDF I was able to make the batch file ready for double sided printing by inserting a blank page after each invoice that had odd number of pages before the file was generated. But now that we switched to wkhtmltopdf I cannot achieve the same result.
The crucial problem is that while wkhtmltopdf allows for the usage of multiple HTML templates there seems to be no reliable way to determine the number of pages they are each going to have before the file is generated. The header and footer templates can receive the page count that the invoice ends up being but they are separate from the main content and therefore I cannot insert a page break accordingly.
I've also tried to calculate the height of the content / PDF page height but there were various issues with that once I started exporting multiple templates (worked alright with a single template). This approach isn't great either because inserting a blank page into the content itself causes the footer to appear on the new page as well which is not what I want.
My best attempt
The only way I've figured out that could get me around these issues is very inefficient. Each time a template is added to the batch I could pre-generate it using a separate instance of a wrapper for wkhtmltopdf, get the temporary file name, determine how many pages it has using pdfinfo and add a blank HTML template to the main instance accordingly. Here's a draft of a function to get the number of pages of the last template added (from a class that extends the wrapper, based on some other pdfinfo questions I found on SO):
/**
* Return the number of pages in the last invoice template added
* $complete === true => return the length of the entire document
*/
public function getNumPages($complete = false)
{
if (!$complete) {
// Generate PDF of last template added
$tmpPdf = new WKPdf($this->_options);
$tmpPdf->addPage($this->content, Array(
'footer-html' => $this->footer
));
/**
The createPdf method is protected so I need to get
the content as string here to force the wrapper to
call wkhtmltopdf.
*/
$tmpPdf->toString();
$document = $tmpPdf->getPdfFilename();
} else {
// Generate entire batch
$this->createPdf();
$document = $this->getPdfFilename();
}
// Use pdfinfo to get the PDF page count
$cmd = 'pdfinfo';
exec("$cmd \"$document\"", $output);
$pagecount = 0;
foreach($output as $op)
{
// Extract the number
if(preg_match("/Pages:\s*(\d+)/i", $op, $matches) === 1)
{
$pagecount = intval($matches[1]);
break;
}
}
return $pagecount;
}
This is very inefficient - it takes about 80 seconds to generate a batch of 25 invoices because I have to call wkhtmltopdf 25 times to create the temporary PDF files so that I can call pdfinfo 25 times to get their individual lengths and insert blank pages where necessary and then generate the final document.
The advantage of TCPDF is that it can give you the number of pages on the fly and a similar functionality takes about 5 seconds to generate a batch file of 25 invoices.
Anyone has any ideas on how to speed things up? Or a better idea to do this altogether. I've considered various tools for the generation including dompdf but wkhtmltopdf is simply the most powerful. The batch generation is really only used from the back office by the store admins so maybe they could be patient. But still.
Unfortunately wkhtmltopdf is the library, which is written in C language and we can not dynamically add one page on the fly like in PHP libraries.
Citate from your comment: Due to number of items ordered or ammount of customer data each invoice can be anywhere from 1 to 3 pages long.
And because of this we can not precalculate the number of pages and write it to a database.
I think you have only one possibility / solution: you have to write behind each invoice a blank page and after the whole PDF was generated you have to edit it with free PHP library like FPDI. In combination with FPDI it is even possible to edit PDF documents.
By PDF editing you could delete all blank pages which you do not need if they starts with odd page number (like 3, 5, etc.). And in FPDI you have the possibility to detect a page number. It is much faster than the solution which you use now.
And the blank(or empty) pages you could detect on content length with FPFI like follows:
<?php
require('fpdf.php');
require_once('setasign/Fpdi/autoload.php');
class Pdf extends \setasign\Fpdi\Fpdi
{
private $pdfReader;
function Header()
{
if(is_null($this->pdfReader))
{
$readerId = $this->getPdfReaderId('blank-pages.pdf');
$this->pdfReader = $this->getPdfReader($readerId);
}
$page_fpdi = $this->pdfReader->getPage($this->PageNo());
$this->content = $page_fpdi->getContentStream();
$this->Cell(0, 15, 'page content length: '.strlen($this->content));
}
protected function _putimages(){}
}
$pdf = new Pdf();
$pdf->SetFont('Arial', '', 12);
$pdf->AddPage(); //page content length: 70 // page with 'Hello World!' string
$pdf->AddPage(); //page content length: 30 // empty page
$pdf->AddPage(); //page content length: 30 // empty page
$pdf->Output();
?>
My blank-pages.pdf I have generated using FPDF with following code:
<?php
require('fpdf.php');
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');
$pdf->AddPage();
$pdf->AddPage();
$pdf->Output();
?>
I am using php MPDF to generate a PDF files it working fine. But i want to set the height of a pdf page as auto based on content size. Content will come dynamically from DB. Anyone give some idea..
The following is my code
$p = 'P';
$mpdf->_setPageSize(array(200, round($mpdf->y)), $p);
this output also not correct
I need to fill in a PDF, in the fly, using PHP - have no idea where to start.
I'm currently doing quizzes on line using PHP - once a series of quizzes are passed the client wants to let the user download a 'certificate of completion'
The PDF of the certificate has blank lines for the users name and the area of study.
My thought is - add 2 form elements to the PDF, and have PHP pill them in when I pull of the certificate.
BUT HOW?
Is there a different, better way?
Things I need to be 'cautious' of - installing third party stuff is not reliable, UNLESS I can just drop a lib in the the site root. I can't guarantee the hosting provider will let me change a PHP config.
Any help is appreciated - the more specific, the better.
Sorry I have no code at the moment - other than how I'm currently displaying the PDF -
// show cert
echo '<iframe src="1_cert.pdf" width="1000" height="700">';
Thanks.
OK - using TCPDF - as suggested - I've installed, and example pages work...
I've placed a file in the example folder.... I've included a call to import...
require_once('../tcpdf_import.php');
// create new PDF document
$pdf = new TCPDF_IMPORT('1_cert.pdf');
...other boiler plate copied form other examples...
$pdf->SetDisplayMode('fullpage', 'SinglePage', 'UseNone');
// set font
$pdf->SetFont('times', 'B', 20);
$pdf->setPage(1, true);
$pdf->SetY(50);
$pdf->Cell(0, 0, 'test text', 1, 1, 'C');
$pdf->lastPage();
The error I'm getting "TCPDF ERROR: Wrong page number on setPage() function: 1"
Ok, if you're not showing code, then neither will I. ;)
I would recommend doing it with TCPDF.
Import the PDF with TCPDF_Import
In a PDF document, you can navigate to any x/y position, (NB: 0/0 is bottom left). Therefore, simply set your “cursor” to the position to each field, and insert a text with either the Cell or the writeHTMLCell method.
Save the PDF document.
Display it to the user.
Voilà.
By the way, both FPDI and TCPDF are common PHP libraries, so you can just put them somewhere in your base folder, no additional tools should be required on a common web server.
I have this set of codes from JpGraph to help me with creating a bar chart.
<?php
require_once ('src/jpgraph.php');
require_once ('src/jpgraph_bar.php');
$datay=array(1992,1993,1995,1996,1997,1998,2001);
// Size of graph
$width=400;
$height=500;
// Set the basic parameters of the graph
$graph = new Graph($width,$height);
$graph->SetScale('textlin');
$top = 60;
$bottom = 30;
$left = 80;
$right = 30;
$graph->Set90AndMargin($left,$right,$top,$bottom);
// Nice shadow
$graph->SetShadow();
// Setup labels
$lbl = array("Andrew\nTait","Thomas\nAnderssen","Kevin\nSpacey","Nick\nDavidsson",
"David\nLindquist","Jason\nTait","Lorin\nPersson");
$graph->xaxis->SetTickLabels($lbl);
// Label align for X-axis
$graph->xaxis->SetLabelAlign('right','center','right');
// Label align for Y-axis
$graph->yaxis->SetLabelAlign('center','bottom');
// Titles
$graph->title->Set('Number of incidents');
// Create a bar pot
$bplot = new BarPlot($datay);
$bplot->SetFillColor('orange');
$bplot->SetWidth(0.5);
$bplot->SetYMin(1990);
?>
However, this will only work if I put it right at the top of my code. If I put it anywhere else, it will fail to display. Is there any way to overcome this so that if I put the code specifically at one place for example under it will appear there? Also, I'll be using some data from my own database as values for this graph.
Thank you.
JpGraph creates an image, which is displayed. You can't output text and images at the same time.
Requires/includes are resolved at runtime, not during parse. This allows them to be invoked dynamically. Therefore, they must be completed before any of their code can be referenced.
Alternative methods to look at are autoload, spl_autoload. This allows class files to be loaded on first reference.
However, from your comments, it appears that the issue is the usage of JpGraph requiring the sending of headers. You'll need to check studentcourse.php to see if any output is generated (including inadvertent whitespace).
Well, you can't escape the fact that when you call new Graph(), the class should already exist, so if you include the files after that line it'll never work.
This generates an image, right?
I would put this in a file by itself, and then on the page where you want the graph to show up, just do:
<img src="myGraph.php">
I'm trying to merge 2 PDF, one on my server (not dynamically generated) and one generated just before the merge and not saved anywhere on the server (I just want my client to download it). So I only have the pdf's content. Both PDF have the same format (A4).
The merged file will have 2 pages and won't be saved on server as well.
Since, I'm using Zend Framework, I would prefer a solution with it (can't find one online...) else any advice ?
(common solution found online but doesn't work)
Edit : because people are lazy to click. The code is in the link anyway since it's wrong and doesn't work.
I try the script below, but I get the
error:
Uncaught exception
'Zend_Pdf_Exception' with message
'Page is attached to one documen, but
rendered in context of another
Alright, with the guide from #Gordon 's comment in my question, I got a solution.
You must have at least Zend Framework 1.11 (I was in 1.9, first error) (found thanks to the 3rd comment to this question)
You must clone page from the PDF you want to merge, else, your application will print an error (self explanatory one) (found thanks to this slideshare which is very interesting for Zend_Pdf)
The static PDF must be a PDF <= 1.4 (mine was 1.6). Zend_Pdf can't parse PDF which version is > 1.4
I used this application to convert the static files I had in version 1.6 to 1.4.
Here's the rough code I have and work (I know it's not optimised, I'll do it later; but still, it can be useful)
$pdf2show = new Zend_Pdf(); // Initializing the merged PDF
$pdf1 = Zend_Pdf::parse($pdfContent, 1); // $pdfContent is the generated one, got the content...
$template = clone $pdf1->pages[0]; // cloning the page (a must do)
$page1 = new Zend_Pdf_Page($template); // Creating the first page of the merged PDF with the previous content
$pdf2show->pages[] = $page1; // Adding this page to the final PDF
$pdf2 = Zend_Pdf::load('urlToYourPDF.pdf'); // Loading the statif PDF
$template2 = clone $pdf2->pages[0]; // cloning the page (a must do)
$page2 = new Zend_Pdf_Page($template2); // Creating the second page of the merged PDF with the previous content
$pdf2show->pages[] = $page2; // Adding this page to the final PDF
sendToWebBrowser('title', $pdf2show->render());
sendToWebBrowser is a function sending the PDF content to browser with the title as... title.
$pdf2show->render() produces the merged PDF content as a string.
$extractor = new Zend_Pdf_Resource_Extractor();
$clone_page_to_use_in_any_pdf = $extractor->clonePage($original_pdf->pages[0]);
This is how I did it.
Hope this helps everyone.
TJ
$extractor = new Zend_Pdf_Resource_Extractor();
$page1 = $extractor->clonePage($pdf->pages[$templatePageIndex1]);
$page2 = $extractor->clonePage($pdf->pages[$templatePageIndex2]);
$page1->drawText('Some text...', $x, $y);
$page2->drawText('Another text...', $x, $y);
$pdf = new Zend_Pdf();
$pdf->pages[] = $page1;
$pdf->pages[] = $page2;
Right from the horses mouth.
http://framework.zend.com/manual/en/zend.pdf.pages.html#zend.pdf.pages.cloning
My experience in merging pdfs :
Zend_Pdf is slow and not efficient for large compilations,
pdftk loose document's bookmarks and crashed if document's size is larger than document merged,
pdflib is really fast and preserve bookmarks, is efficient for large compilations.
Have you tried merging them using the PDF toolkit?
Merging multiple PDFs with it can be achieved using :
pdftk 1.pdf 2.pdf 3.pdf cat output 123.pdf
123.pdf will be the resulting PDF. You can temporarily store the resulting PDF on the server, send it to the browser and remove it when it's no longer needed.