Solved

PDF converter for a PHP LAMP environment

Posted on 2013-12-05
7
685 Views
Last Modified: 2013-12-05
Hi,

We are creating a final Assessment page in Limesurvey (for those who know Limesurvey). It's a LAMP environment.

I had originally thought that we needed a PHP HTML to PDF converter, but reading through the various historical posts there appear to be too many problems with this approach, and what we really need is a converter for our core data model which is PHP based.  However most of the posts are several years old and I wonder if the world has moved on since then?

I believe therefore that we want to source a converter to be able create a PDF document with the following features:

Independent of Browser - Does not depend on the Web Browser
Full CSS support
Auto/Manual paging
Text and Links preservered (not a screenshot)
Handles nested tables

It would be nice if it has a (switchable) JS engine built in, but this is not a prerequisite.

Free (GPL 2) source would be good but happy to pay a reasonable price (up to a few hundred bucks) if we get the right features set out of the box, but not thousands of dollars, and definitely not on a per page pricing model.

Any tips, hints or corrections would also be greatly appreciated :)

Many thanks
Chris
0
Comment
Question by:Chris_Ryan
7 Comments
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39697750
Try Apache FOP. Browser independent and free.

https://xmlgraphics.apache.org/fop/

HTH,
Dan
0
 
LVL 10

Expert Comment

by:ienaxxx
ID: 39697835
PDFLib -> payment but powerful

fpdf -> free and works good. Look in the site for community libraries and functions: there should be at least one to convert from HTML to PDF. (To don't reinvent the wheel...)

Hope this helps.
0
 
LVL 108

Accepted Solution

by:
Ray Paseur earned 500 total points
ID: 39698097
Full CSS support
This is a bit of an oxymoron.  CSS is one of the client-side components of the browser rendering process, intended to give visual shape to what would otherwise be nothing but semantic markup.  In contrast, PDF is a page-layout language.  These are "fish and fowl," very different since the author of the page layout obtains precise control over the size and shape of the page, whereas the author of the CSS lives with the variability of the client viewport and display settings.  And not to put too fine a point on it, but the languages are completely different in grammar, syntax, verbs, etc.

In my experience, "converting" HTML to PDF is a fools errand, with more risk than success.  A better way to frame the problem is to start with the data model, lay out the PDF document and make that link directly.  Just never think about HTML, CSS, JavaScript or any of that client-side stuff during the document creation process.  If you do it that way you will save yourself from a lot of the unpleasant surprises that show up when you try to inject incompatible notions of markup, animation and styling into the PDF design process.

I haven't used FOP, but I have used both FPDF and TCPDF with great success.  The positioning is highly accurate, down to 1/100 inch, color controls are flawless, image rendering is fidelic, etc.  That said, documentation for these classes is not perfect.  It takes quite a bit of work and some trial and error to get your data rendering exact, and you need to build the time for this work into your schedule.

An FPDF example:
http://www.laprbass.com/RAY_fpdf_hello_world.php
<?php // RAY_fpdf_hello_world.php
error_reporting(E_ALL);


// DEMONSTRATE SOME OF THE BASICS OF FPDF


// SOME VARIABLES FOR OUR TESTS (COULD COME FROM DATA BASE, ETC)
$font = 'Arial';
$text = 'Hello World!';

// BRING IN THE PDF THING
require_once('fpdf16/fpdf.php');

// SYNTHESIZE THE PDF FILE INFORMATION
$pdf_file_link
= DIRECTORY_SEPARATOR
. 'RAY_junk'
. DIRECTORY_SEPARATOR
. 'temp_pdf_blue'
. '.pdf'
;
$pdf_file_name
= getcwd()
. $pdf_file_link
;

// DO THE HELLO WORLD EXERCISE IN WHITE ON A BLUE BACKGROUND
$pdf=new FPDF();
$pdf->AddPage();
$pdf->SetFont($font, 'B', 16);
$pdf->SetFillColor(  0,   0, 255);
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(40, 10, $text, 0, 2, 'L', TRUE);

// WRITE THE PDF TO DISK
$pdf->Output($pdf_file_name, 'F');

// PRESENT A LINK
echo '<a target="my_PDF" href="' . $pdf_file_link . '">Blue PDF</a>';



// START OVER FOR A NEW PDF
unset($pdf);
echo PHP_EOL . "<br/>";

// SYNTHESIZE THE PDF FILE INFORMATION
$pdf_file_link
= DIRECTORY_SEPARATOR
. 'RAY_junk'
. DIRECTORY_SEPARATOR
. 'temp_pdf_red'
. '.pdf'
;
$pdf_file_name
= getcwd()
. $pdf_file_link
;

// DO THE HELLO WORLD EXERCISE IN WHITE ON A RED BACKGROUND
$pdf=new FPDF();
$pdf->AddPage();
$pdf->SetFont($font, 'B', 16);
$pdf->SetFillColor(255,   0,   0);
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(40, 10, $text, 0, 2, 'L', TRUE);

// WRITE THE PDF TO DISK
$pdf->Output($pdf_file_name, 'F');

// PRESENT A LINK
echo '<a target="my_PDF" href="' . $pdf_file_link . '">Red PDF</a>';

Open in new window

A TCPDF Example:
http://www.laprbass.com/RAY_tcpdf_image_position.php
<?php // RAY_tcpdf_image_position.php
error_reporting(E_ALL);
date_default_timezone_set('America/Chicago');

// SYNTHESIZE THE PDF FILE NAME
$pdf_file_link    = '/RAY_tcpdf_example.pdf';
$pdf_file_name    = getcwd() . $pdf_file_link;

// THE TCPDF CLASS AND CONFIGURATION
require_once('tcpdf/config/lang/eng.php');
require_once('tcpdf/tcpdf.php');

// EXTEND THE TCPDF OBJECT
class PDF extends TCPDF
{
    // NULLIFY AUTOMATIC HEADER AND FOOTER
    public function Header() {}
    public function Footer() {}
}

echo "<br />Starting PDF creation" . PHP_EOL;

// INSTANTIATE THE OBJECT
$pdf = new PDF('P', 'mm', 'LETTER', true, 'UTF-8', false);
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
$pdf->SetMargins(0,0,0,TRUE);
$pdf->SetAutoPageBreak(FALSE);
$pdf->setLanguageArray($l);
$pdf->SetFont('times', '', 13);
$pdf->setCellPaddings(0,0,0,0);
$pdf->setCellMargins(0,0,0,0);

// DO NOT DO THIS
// $pdf->setFillColor(255,255,224);


// CELL BORDERS, IF NEEDED
$bdr = '1TRBL';

// IF NO CELL BORDER IS NEEDED
$bdr = 0;

// ADD A PAGE
$pdf->AddPage('P', 'LETTER', TRUE);

// ADD AN IMAGE
// http://www.tcpdf.org/doc/classTCPDF.html#a714c2bee7d6b39d4d6d304540c761352

$img = 'RAY_EE_images/ray_padded.png';

// I HAVE NO IDEA WHAT THIS IS DOING TO IMAGE SCALE - EXPERIMENT WITH IT
$pdf->setImageScale(1.53);

// GET THE IMAGE, I HOPE
$pdf->Image
( $img
, 0             // $x
, 0             // $y
, 0             // WIDTH
, 0             // HEIGHT
, 'PNG'         // TYPE
, '#'           // LINK URL
, 'T'           // SET POINTER TOP LEFT
, FALSE         // NO RESIZING
, 300           // DPI
, 'L'           // PALIGN
, FALSE         // ISMASK
, FALSE         // IMGMASK
, 0             // BORDER
, FALSE         // FIT TO BOX
, FALSE         // HIDDEN
, FALSE         // FIT ON PAGE
)
;

// ADD SOME TEXT ON TOP OF THE IMAGE
$pdf->Text
( 24            // $x,
, 24            // $y,
, 'HELLO WORLD'
, FALSE         // $fstroke = false,
, FALSE         // $fclip = false,
, TRUE          // $ffill = true,
, $bdr          // $border = 0,
, 2             // $ln = 0, 2=PUT CURSOR BELOW
, ''            // $align = '', DEFAULT LEFT
, FALSE         // $fill = false, CELL BACKGROUND
, ''            // $link = '', NOT A LINK
, 0             // $stretch = 0, NO TEXT STRETCH
, FALSE         // $ignore_min_height = false,
, 'A'           // $calign = 'T', A=FONT TOP INSIDE CELL
, 'T'           // $valign = 'M', T=VERTICAL ALIGN INSIDE CELL
, TRUE          // $rtloff = false TRUE = USE PAGE TOP LEFT CORNER TO ALIGN
)
;

// WRITE THE PDF FILE TO THE SERVER
echo "<br />Writing PDF\n";
$pdf->Output($pdf_file_name,'F');

// PRESENT A CLICKABLE LINK SO WE CAN D/L AND PRINT THE PDF
echo "<br /><a target=\"my_PDF\" href=\"$pdf_file_link\"><strong>Print the PDF</strong></a>";

Open in new window

HTH, ~Ray
0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 14

Expert Comment

by:Giovanni Heward
ID: 39698469
I've had the best results with Prince @ http://www.princexml.com

I came across this source the other day:

require('prince.php');
ob_start();
include('report.php');
$dir = dirname(__FILE__);
$out = str_replace('folder/', "$dir/folder/", ob_get_clean());
$out = str_replace('\\', '/', $out);
$style = '	@page {
		margin: 50pt 20pt 40pt 20pt ;
		prince-shrink-to-fit: auto;
	}
	body{background-color:#fff}';
$out = str_replace('body{background-color:#fff}', $style, $out);

if (preg_match('/(qr\.php\?txt=)(.*)(") /i', $out, $regs)) $out = str_replace($regs[0], "$dir\\reports\\$dest.png\" ", $out);

$prince = new Prince('/www/bin/prince/bin/prince.exe');
$prince->setHTML(true);
#$prince->setJavaScript(true);
#$prince->setLog("$dir\\prince.log");
$filename = $dir . "\\reports\\$dest.pdf";
$prince->convert_string_to_file($out, $filename, $var);

// patch watermark
$file = file_get_contents($filename);
$pattern = array(
	'%/Producer \(Prince \d+[.]?\d+( rev \d+)? \\\\\(www\.princexml\.com\\\\\)\).%si',
	'%(<</Type /Annot).(/Rect \[572\.0000 752\.0000 597\.0000 777\.0000\]).(/Border \[0 0 0\]).(/AP <</N \d+ \d+ R>>).(/BS <</Type /Border).(/W 2).(/S /S>>).(/Subtype /Text).(/T \(www\.princexml\.com\)).(/Subj \(Prince - Non-commercial License\)).(/Contents \(This document was created with Prince, a great way of getting web content onto paper\.\)).(/Popup \d+ \d+ R).(/Name /Note>>).%si',
	'<</Type /Annot
/Rect [370.6650 296.4787 482.8567 307.3713]
/Border [0 0 0]
/Subtype /Link
/A <</Type /Action
/S /URI
/URI (https://www.example.com/)>>>>
');
if (preg_match($pattern[0], $file)) {
	$file = preg_replace($pattern[0], '', $file);
}
if (preg_match($pattern[1], $file)) {
	$file = preg_replace($pattern[1], $pattern[2], $file);
}
file_put_contents($filename, $file);

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-type: application/pdf');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
header("Content-Disposition: $mode; filename=\"$dest.pdf\"");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($filename));
header('Accept-Ranges: bytes');
header('Connection: close');
@readfile($filename);
exit;

Open in new window


Though I don't think $3800 for one license meets your qualifications.  :-)
0
 

Author Comment

by:Chris_Ryan
ID: 39698490
Hi Ray,

Thanks for your sage advice.  Obviously the voice of experience here :)  and thanks so much for the example code.  I take it that nested tables and keeping links is no problem with either of the suggested solutions?

Hi x66_x72_x65_x65,

Thanks so much for taking the time to provide the Prince solution.  Unfortunately this breaks my price barrier, costing about $4,000 for a single server solution (and we would need two servers for back up).

Thanks also to everyone else for their contribution

Kind regards
Chris
0
 
LVL 14

Expert Comment

by:Giovanni Heward
ID: 39698507
@Chris_Ryan, I agree.  There is some valuable info the glean form the source, however.  I've found the following method particularly useful in rendering css:

ob_start();
include('source.php');
$out = str_replace('folder/', "$dir/folder/", ob_get_clean());
$out = str_replace('\\', '/', $out);

Open in new window


etc.
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 39698575
Thanks for the points.  Links are no problem.  The whole idea of "nested tables" goes away in PDF layout.  Tables are an artifact of HTML-think, and with PDF you get to place the data exactly where you want it on the page with X/Y coordinates.  Good luck with it, ~Ray
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

International Data Corporation (IDC) prognosticates that before the current the year gets over disbursing on IT framework products to be sent in cloud environs will be $37.1B.
It’s a strangely common occurrence that when you send someone their login details for a system, they can’t get in. This article will help you understand why it happens, and what you can do about it.
The viewer will learn how to count occurrences of each item in an array.
The viewer will learn how to create a basic form using some HTML5 and PHP for later processing. Set up your basic HTML file. Open your form tag and set the method and action attributes.: (CODE) Set up your first few inputs one for the name and …

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now