I'm working on a PHP web project using FPDF library, which is working fine. My ongoing task is to insert a table in the generated pdf page. Since I need to output the table in a specific way (see picture below), it is being very difficult to use the existing scripts one can find here : http://www.fpdf.org/fr/script/index.php. I'm using the classes provided in the link as traits in my custom PDF class.
The specific way of rendering data:
colonne (French) = column (English) / ligne = line
Here is the code i'm using:
Any ideas about a library that can help me get a html table inside pdf page with overlap issue and the possibility to change table html properties?
Update: Oct 10, 2019 I've just found answer to the overlap issue in the code below written by Olivier (http://www.fpdf.org/en/script/script3.php):
<?php
require('fpdf.php');
class PDF_MC_Table extends FPDF
{
var $widths;
var $aligns;
function SetWidths($w)
{
//Set the array of column widths
$this->widths=$w;
}
function SetAligns($a)
{
//Set the array of column alignments
$this->aligns=$a;
}
function Row($data)
{
//Calculate the height of the row
$nb=0;
for($i=0;$i<count($data);$i++)
$nb=max($nb,$this->NbLines($this->widths[$i],$data[$i]));
$h=5*$nb;
//Issue a page break first if needed
$this->CheckPageBreak($h);
//Draw the cells of the row
for($i=0;$i<count($data);$i++)
{
$w=$this->widths[$i];
$a=isset($this->aligns[$i]) ? $this->aligns[$i] : 'L';
//Save the current position
$x=$this->GetX();
$y=$this->GetY();
//Draw the border
$this->Rect($x,$y,$w,$h);
//Print the text
$this->MultiCell($w,5,$data[$i],0,$a);
//Put the position to the right of the cell
$this->SetXY($x+$w,$y);
}
//Go to the next line
$this->Ln($h);
}
function CheckPageBreak($h)
{
//If the height h would cause an overflow, add a new page immediately
if($this->GetY()+$h>$this->PageBreakTrigger)
$this->AddPage($this->CurOrientation);
}
function NbLines($w,$txt)
{
//Computes the number of lines a MultiCell of width w will take
$cw=&$this->CurrentFont['cw'];
if($w==0)
$w=$this->w-$this->rMargin-$this->x;
$wmax=($w-2*$this->cMargin)*1000/$this->FontSize;
$s=str_replace("\r",'',$txt);
$nb=strlen($s);
if($nb>0 and $s[$nb-1]=="\n")
$nb--;
$sep=-1;
$i=0;
$j=0;
$l=0;
$nl=1;
while($i<$nb)
{
$c=$s[$i];
if($c=="\n")
{
$i++;
$sep=-1;
$j=$i;
$l=0;
$nl++;
continue;
}
if($c==' ')
$sep=$i;
$l+=$cw[$c];
if($l>$wmax)
{
if($sep==-1)
{
if($i==$j)
$i++;
}
else
$i=$sep+1;
$sep=-1;
$j=$i;
$l=0;
$nl++;
}
else
$i++;
}
return $nl;
}
}
?>
Now the only problem I have is editing Row()
function in the above code so the table is rendered in the specific way I mentioned.
This is how Row()
function is used as stated in the documentation of the above class (see http://www.fpdf.org/fr/script/script3.php):
function GenerateWord()
{
//Get a random word
$nb=rand(3,10);
$w='';
for($i=1;$i<=$nb;$i++)
$w.=chr(rand(ord('a'),ord('z')));
return $w;
}
function GenerateSentence()
{
//Get a random sentence
$nb=rand(1,10);
$s='';
for($i=1;$i<=$nb;$i++)
$s.=GenerateWord().' ';
return substr($s,0,-1);
}
$pdf=new PDF_MC_Table();
$pdf->AddPage();
$pdf->SetFont('Arial','',14);
//Table with 20 rows and 4 columns
$pdf->SetWidths(array(30,50,30,40));
srand(microtime()*1000000);
for($i=0;$i<20;$i++)
$pdf->Row(array(GenerateSentence(),GenerateSentence(),GenerateSentence(),GenerateSentence()));
$pdf->Output();
In my case the array to pass on to Row function is a tridimensional one:
$table =
Array
(
[1] => Array
(
[1] => Array
(
[1] => colonne1 ligne1
)
[2] => Array
(
[4] => colonne2 ligne1
)
[3] => Array
(
[9] => colonne3 ligne1
)
)
[pos_paragr_prec] => 0
[2] => Array
(
[1] => Array
(
[2] => colonne1 ligne2
)
[2] => Array
(
[7] => colonne2 ligne2
)
[3] => Array
(
[10] => colonne3 ligne2
)
)
[3] => Array
(
[1] => Array
(
[3] => colonne1 ligne3
)
[2] => Array
(
[8] => colonne2 ligne3
)
[3] => Array
(
[11] => colonne3 ligne3
)
)
)
Any hints on how the Row function can be edited to produce the table in the way mentioned above?
So far, I've tried to do something like this without editing the Row function:
if (count($table) > 0) {
foreach($table as $row_array) {
foreach($row_array as $key => $val) {
for($i=0;$i<count($row_array);$i++) {
$pdf->Row($val);
}
}
}
}
This, obviously, doesn't work.
Update
I have made some improvements in my code this way:
if (count($table) > 0) {
if (is_array($table_data) && count($table_data) > 0) {
foreach ($table_data as $key => $value) {
$arrayWidths = [];
foreach($value as $key2 => $val) {
array_push($arrayWidths, 40);
$pdf->SetWidths($arrayWidths);
foreach($val as $k => $col) {
$w=40;//$pdf->widths[$key2];
$a='L';//isset($pdf->aligns[$i]) ? $pdf->aligns[$key2] : 'L';
//Save current position
$x=$pdf->GetX();
$y=$pdf->GetY();
//Drawing the border
$pdf->Rect($x,$y,$w,$h);
//Printing text
$pdf->MultiCell($w,5,$table_data[$key][$key2][$k],0,$a);
//Putting the position to the right of the cell
$pdf->SetXY($x+$w,$y);
}
}
$pdf->Ln();
}
}
}
By now data are being rendered the specific way I wanted. However, there is a difference between the first row and the rest. The first row is acceptable. I have the overlap issue with those rows below the first one. Here is the result that I've got:
I used the logic of this script : http://www.fpdf.org/en/script/script3.php (see the code pasted above).
Any ideas? Thanks in advance
I've been able to produce a table the right way by rewriting the code available here, so it fits the specificity of my context: fpdf.org/en/script/script14.php. Below is what I did.
I created the following trait, which I used inside PDF class :
<?php
trait PDF_SQL_Table
{
protected $aCols=array();
protected $TableX;
protected $HeaderColor;
protected $RowColors;
protected $ColorIndex;
/**
* @return void
*/
function TableHeader()
{
$this->SetFont('Arial','B',10);
$this->SetTextColor(0); ## Couleur de police de caractères de l'en-tête du tableau
## Couleur de fond de l'en-tête du tableau
$fill=!empty($this->HeaderColor);
if($fill)
$this->SetFillColor($this->HeaderColor[0],$this->HeaderColor[1],$this->HeaderColor[2]);
}
/**
* @param array $tableData
* @param $width
* @param $align
* @param $TableWidth
* @param $key
* @return float|int
*/
function CalcWidths(array $tableData, $width, $align, $TableWidth, $key)
{
$w = $this->aCols[$key]['w'];
if($w==-1)
$w = $width/count($tableData);
elseif(substr($w,-1) == '%')
$w = $w/100 * $width;
$this->aCols[$key]['w'] = $w;
$TableWidth += $w;
// Calcul de l'abscisse du tableau
if($align=='C')
$this->TableX=max(($this->w-$TableWidth)/2,0);
elseif($align=='R')
$this->TableX= $this->TableX=max($this->w-$this->rMargin-$TableWidth,0);//max($this->w-15-$TableWidth,0); //$this->TableX=max($this->w-$this->rMargin-$TableWidth,0);
else
$this->TableX=$this->lMargin;//15; // $this->TableX=$this->lMargin;
return $w;
}
/**
* @param int $field
* @param int $width
* @param string $caption
* @param string $align
*/
function AddCol($field=-1, $width=-1, $caption='', $align='L'): void
{
// Ajout d'une colonne du tableau
if($field == -1)
$field = count($this->aCols);
$this->aCols[$field]=array('f'=>$field,'c'=>$caption,'w'=>$width,'a'=>$align);
}
/**
* @param array $table
* @param array $prop
* @param array $array_paragraphes
* @param float $interligne
* @param array $excludedParagraphs
* @param $dec
*/
function TableAndParagraphs(array $table = array(), array $prop=array(), array $array_paragraphes, float $interligne, array $excludedParagraphs, $dec): void
{
foreach ($array_paragraphes as $ordre => $paragraphe) {
### $this->Ln(10); En activant ceci à cet endroit précis, un des tableaux s'affiche sur plusieurs pages de façon discontinue
### Insertion d'un tableau au dessous du paragraphe correspondant à ordre_texte_saisi = $table['pos_paragr_prec']
### Utilisation d'une extension de la librairie FPDF disponible ici (que nous avons personnalisée) : http://www.fpdf.org/?go=script&id=14
$counter = 0;
foreach ($table as $num_tableau => $donnees_tableau) {
## Insertion d'un tableau au dessous du paragraphe correspondant à ordre_texte_saisi = $table['pos_paragr_prec']
## Utilisation d'une extension de la librairie FPDF disponible ici : http://www.fpdf.org/?go=script&id=14
// Gestion des propriétés
if(!isset($prop['width']))
$prop['width']=0;
if($prop['width']==0)
$prop['width']= $this->w-$this->lMargin-$this->rMargin;//$this->w-15-15; //($this->lMargin = 15 / $this->rMargin = 15 cf. plus haut)
if(!isset($prop['align']))
$prop['align']='C';
if(!isset($prop['padding']))
$prop['padding']=$this->cMargin;
$cMargin=$this->cMargin;
$this->cMargin=$prop['padding'];
if(!isset($prop['HeaderColor']))
$prop['HeaderColor']=array();
$this->HeaderColor=$prop['HeaderColor'];
if(!isset($prop['color1']))
$prop['color1']=array();
if(!isset($prop['color2']))
$prop['color2']=array();
$this->RowColors=array($prop['color1'],$prop['color2']);
$this->ColorIndex = 0;
if ($ordre === ($table[$num_tableau]['pos_paragr_prec'])) {
$excludedParagraphs[] = $ordre;
### $this->Ln(10); En activant ceci un des tableaux s'affiche sur plusieurs pages de façon discontinue
$paragraphe = html_entity_decode($paragraphe, ENT_HTML5, "UTF-8");
$this->MultiCell(0,$interligne,$paragraphe);
### $this->Ln($dec); En activant ceci, à partir de la 2ème ligne du tableau l'affichage se fait sur plusieurs pages avec une cellule par page
if (count($table) > 0) {
$this->Ln(10); ### Un espace vertical avant d'afficher le tableau
if (is_array($donnees_tableau) && count($donnees_tableau) > 0) {
$iteration = 0; ### indice d'itération sur les tableaux
$TableWidth=0;
foreach ($donnees_tableau as $key => $value) { ### Début d'affichage d'un tableau
### Si la hauteur h = 20 provoque un débordement, on effectue un saut de page manuel
### $this->Ln(0); juste un test : activer ceci donne une idée sur le traitement ligne par ligne (ceci produit une nette séparation entre les lignes)
$this->CheckPageBreak(10);// ceci permet de pouvoir afficher un tableau sur 2 ou plusieurs pages
$this->SetX(15);// On décale le tableau vers la droite de 18 unités (en argument de la fonction SetX()). Permet de déplacer le tableau horizontalement
$number = 0; // Itération sur les données du tableau
foreach($value as $key2 => $val) {
foreach($val as $k => $col) {
// Ajout dynamique d'une colonne au tableau
$this->AddCol($key2, -1, '', 'R');
// Calcul dynamique de la largeur de la colonne
$w = $this->CalcWidths($value, $prop['width'], 'L', $TableWidth, $key2);
$ci = $this->ColorIndex;
$fill=!empty($this->RowColors[$ci]);
if($fill)
$this->SetFillColor($this->RowColors[$ci][0],$this->RowColors[$ci][1],$this->RowColors[$ci][2]);
$this->SetFont('Arial','',9); // Police de caractère pour les tableaux
if (is_numeric($donnees_tableau[$key][$key2][$k])) {
$cellData = number_format($donnees_tableau[$key][$key2][$k], 2, ',',' ');
$alignRight = true;
} elseif (substr($donnees_tableau[$key][$key2][$k], -1) == '%') {
$cellData = number_format($donnees_tableau[$key][$key2][$k], 2, ',',' ').'%';
$alignRight = true;
} elseif (preg_match('/^[0-9+-]{1,3}(\s|,)[0-9]{1,3}\s?[0-9]{0,3},?[0-9]{0,2}/', $donnees_tableau[$key][$key2][$k])) {
$cellData = $donnees_tableau[$key][$key2][$k];
$alignRight = true;
} elseif (preg_match('/[0-9]{1,3},[0-9]{3}\.?[0-9]{0,2}/', $donnees_tableau[$key][$key2][$k])) {
$cellData = str_replace(',', ' ', $donnees_tableau[$key][$key2][$k]);
$cellData = str_replace('.', ',', $cellData);
$alignRight = true;
} elseif (preg_match('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{2,4})$/', $donnees_tableau[$key][$key2][$k])) {
//$cellData = preg_replace('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{2,4})$/', "$2/$1/$3", $donnees_tableau[$key][$key2][$k]);
$cellData = date_create($donnees_tableau[$key][$key2][$k]);
$cellData = date_format($cellData, "d/m/Y");
$alignRight = false;
} else {
$cellData = $donnees_tableau[$key][$key2][$k];
$alignRight = false;
}
$x=$this->GetX();
$y=$this->GetY();
## On évite qu'il y ait des Multicells de hauteur inférieure à celle du Cell suivant sur la même ligne
if ($this->GetStringWidth($donnees_tableau[$key][$key2][$k]) < $w && $key2 == '0' /*&& $key >= '1'*/) {
$spaces = "\n"." "; // Espace insécable : " "
$cellData .= $spaces;
}
## Mise en forme du header (police de caractère et couleur de l'arrière-plan)
if ($key == '0') {
$this->TableHeader();
} else {
$this->SetTextColor(0);
}
$this->SetDrawColor(0); ### couleur de la bordure des cellule du tableau : valeur de 0 (noir) à 255 (blanc)
if ($this->GetStringWidth($donnees_tableau[$key][$key2][$k]) > $w || $key2 === 'A' || $key2 == '0') {
$this->MultiCell($w,5,$cellData,1,'L',$fill);
$newY = $this->GetY();
$this->SetXY($x + $w, $y);
} else {
$H = $newY - $y;
if ($alignRight) {
$this->Cell($w, $H,$cellData,1,1,'R',$fill);
} else {
$this->Cell($w, $H,$cellData,1,1,'C',$fill);
}
}
$this->SetFont('Arial','',11);
$this->SetDrawColor(0);
$this->SetXY($x+$w,$y);
}
$number++;
}// affichage d'une ligne entière
if ($iteration == 1) {
$this->Ln(0); ###### 3 : valeur représentant l'écart entre le header du tableau et le corps du tableau
} else {
$this->Ln($H);
}
// Retour à la ligne afin d'afficher la ligne suivante du tableau
$this->ColorIndex = 1-$ci;
$iteration++;
}// Un tableau entier est affiché
}
}
}//if ($ordre === ($table[$num_tableau]['pos_paragr_prec']))
//Espace entre le premier tableau et le paragraphe en dessous
if ($counter == 0) {
$this->Ln(5.5);
}
//Empêcher qu'un paragraphe ne s'affiche plusieurs fois
if ($counter == count($table) - 1) {
//Les paragraphes liés à des tableaux ont été déjà affichés
if (!in_array($ordre, $excludedParagraphs)) {
$this->Ln(5.5);// Espace entre un tableau et le paragraphe en dessous (premier tableau exclus)
$paragraphe = html_entity_decode($paragraphe, ENT_HTML5, "UTF-8");
$this->MultiCell(0,$interligne,$paragraphe);
$this->Ln($dec);
}
}
$counter++;
}
}
}
}
PDF class:
class PDF extends TFPDF
{
protected $B = 0;
protected $I = 0;
protected $U = 0;
protected $HREF = '';
use PDF_MC_Table, PDF_SQL_Table;
function Entete() {
}
function Footer() {
//some code
}
//etc.
}
In the controller, I could call TableAndParagraphs() function this way:
$pdf->TableAndParagraphs($table, $prop, $array_paragraphes, $interligne, $excludedParagraphs, $dec);
Note that TFPDF is a library based on FPDF (it can be found on Internet):
<?php
/*******************************************************************************
* tFPDF (based on FPDF 1.7) *
* *
* Version: 1.24 *
* Date: 2011-09-24 *
* Author: Ian Back <ianb@bpm1.com> *
* License: LGPL *
*******************************************************************************/
define('tFPDF_VERSION','1.24');
class tFPDF
{
var $unifontSubset;
var $page; // current page number
var $n; // current object number
var $offsets; // array of object offsets
var $buffer; // buffer holding in-memory PDF
var $pages; // array containing pages
var $state; // current document state
var $compress; // compression flag
var $k; // scale factor (number of points in user unit)
var $DefOrientation; // default orientation
var $CurOrientation; // current orientation
var $StdPageSizes; // standard page sizes
var $DefPageSize; // default page size
var $CurPageSize; // current page size
var $PageSizes; // used for pages with non default sizes or orientations
var $wPt, $hPt; // dimensions of current page in points
var $w, $h; // dimensions of current page in user unit
var $lMargin; // left margin
var $tMargin; // top margin
var $rMargin; // right margin
var $bMargin; // page break margin
var $cMargin; // cell margin
var $x, $y; // current position in user unit
var $lasth; // height of last printed cell
var $LineWidth; // line width in user unit
var $fontpath; // path containing fonts
var $CoreFonts; // array of core font names
var $fonts; // array of used fonts
var $FontFiles; // array of font files
var $diffs; // array of encoding differences
var $FontFamily; // current font family
var $FontStyle; // current font style
var $underline; // underlining flag
var $CurrentFont; // current font info
var $FontSizePt; // current font size in points
var $FontSize; // current font size in user unit
var $DrawColor; // commands for drawing color
var $FillColor; // commands for filling color
var $TextColor; // commands for text color
var $ColorFlag; // indicates whether fill and text colors are different
var $ws; // word spacing
var $images; // array of used images
var $PageLinks; // array of links in pages
var $links; // array of internal links
var $AutoPageBreak; // automatic page breaking
var $PageBreakTrigger; // threshold used to trigger page breaks
var $InHeader; // flag set when processing header
var $InFooter; // flag set when processing footer
var $ZoomMode; // zoom display mode
var $LayoutMode; // layout display mode
var $title; // title
var $subject; // subject
var $author; // author
var $keywords; // keywords
var $creator; // creator
var $AliasNbPages; // alias for total number of pages
var $PDFVersion; // PDF version number
/*******************************************************************************
* *
* Public methods *
* *
*******************************************************************************/
function __construct($orientation='P', $unit='mm', $size='A4')
{
// Some checks
$this->_dochecks();
// Initialization of properties
$this->page = 0;
$this->n = 2;
$this->buffer = '';
$this->pages = array();
$this->PageSizes = array();
$this->state = 0;
$this->fonts = array();
$this->FontFiles = array();
$this->diffs = array();
$this->images = array();
$this->links = array();
$this->InHeader = false;
$this->InFooter = false;
$this->lasth = 0;
$this->FontFamily = '';
$this->FontStyle = '';
$this->FontSizePt = 12;
$this->underline = false;
$this->DrawColor = '0 G';
$this->FillColor = '0 g';
$this->TextColor = '0 g';
$this->ColorFlag = false;
$this->ws = 0;
// Font path
if(defined('FPDF_FONTPATH'))
{
$this->fontpath = FPDF_FONTPATH;
if(substr($this->fontpath,-1)!='/' && substr($this->fontpath,-1)!='\\')
$this->fontpath .= '/';
}
elseif(is_dir(dirname(__FILE__).'/font'))
$this->fontpath = dirname(__FILE__).'/font/';
else
$this->fontpath = '';
// Core fonts
$this->CoreFonts = array('courier', 'helvetica', 'times', 'symbol', 'zapfdingbats');
// Scale factor
if($unit=='pt')
$this->k = 1;
elseif($unit=='mm')
$this->k = 72/25.4;
elseif($unit=='cm')
$this->k = 72/2.54;
elseif($unit=='in')
$this->k = 72;
else
$this->Error('Incorrect unit: '.$unit);
// Page sizes
$this->StdPageSizes = array('a3'=>array(841.89,1190.55), 'a4'=>array(595.28,841.89), 'a5'=>array(420.94,595.28),
'letter'=>array(612,792), 'legal'=>array(612,1008));
$size = $this->_getpagesize($size);
$this->DefPageSize = $size;
$this->CurPageSize = $size;
// Page orientation
$orientation = mb_strtolower($orientation); // 20160707 ajout mb_
if($orientation=='p' || $orientation=='portrait')
{
$this->DefOrientation = 'P';
$this->w = $size[0];
$this->h = $size[1];
}
elseif($orientation=='l' || $orientation=='landscape')
{
$this->DefOrientation = 'L';
$this->w = $size[1];
$this->h = $size[0];
}
else
$this->Error('Incorrect orientation: '.$orientation);
$this->CurOrientation = $this->DefOrientation;
$this->wPt = $this->w*$this->k;
$this->hPt = $this->h*$this->k;
// Page margins (1 cm)
$margin = 28.35/$this->k;
$this->SetMargins($margin,$margin);
// Interior cell margin (1 mm)
$this->cMargin = $margin/10;
// Line width (0.2 mm)
$this->LineWidth = .567/$this->k;
// Automatic page break
$this->SetAutoPageBreak(true,2*$margin);
// Default display mode
$this->SetDisplayMode('default');
// Enable compression
$this->SetCompression(true);
// Set default PDF version number
$this->PDFVersion = '1.3';
}
//etc.
}