<?php
/*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
XML Template to PDF Class v1.1
Copyright (C) 2001, Edward Rudd
*/
/*
Include Manuel Lemos's XML_Parser class.
I'll eventually rewrite my parser...
But this gets the new functionality working..
*/
require_once "xml_parser.php";
class PDFTemplate {
var $pdf; //PDF File
var $parser; //XML Parsed data (xml_parse object)
var $debug; //Debug??
var $page; //Current Page definition
/*
closed = Has the page been "ended" for checks on whether to PDF endpage or PDF beginpage
fontlead = the height of one line (1.5 times the height of the current font)
margin = the surrounding margins (default half inch, 36 units)
width = the width of the page (default 8.5 inches, 612 units)
height = the height of a page (default 11 inches, 792 units)
curX = the current X position for font placement (left is 0)
curY = the current Y position for font placement (bottom of the page is 0)
tab = the default tab setting,also used for initial indent (default is half inch, 36 units)
left = used for block indent, set on PARA tag load for new lines
align = justification
ticks = enable ticks
block[name] = Current repeated block
block[count] = Current iteration in block
*/
var $fontstack; //font stack
/*
Font = array of Font names
Size = array of Font sizes
*/
var $subs; //complex array substitutions (global substitutions)
/*
key = array of keys in {VARNAME} format
val = array of values to substitute keys
*/
var $blocksubs; //substitutions for BLOCKS
/*
*/
var $filename; //XML Filename
//Class initializer. the XML filename and optionally enable debug (set to 1)
//Also sends PDF content-type header;
function PDFTemplate($filename,$debug=0) {
// Initialization
$this->debug = $debug;
$this->filename = $filename;
$this->subs["keys"] = array(); //Variables to Substiture with.
$this->subs["vals"] = array(); //Variables to Substiture with.
$this->cmnAssign(&$this->subs["keys"],&$this->subs["vals"],"DEFAULTS.VERSION","PDFTemplate v1.1.1");
$this->cmnAssign(&$this->subs["keys"],&$this->subs["vals"],"DEFAULTS.DATE",date("F j, Y"));
$this->cmnAssign(&$this->subs["keys"],&$this->subs["vals"],"DEFAULTS.TIME",date("g:m A"));
$this->fontstack["Font"] = array();
$this->fontstack["Size"] = array();
$this->addfont("Helvetica",12);
//Setup PDF Docuemnt
$this->pdf = PDF_open();
//Send Headers
if ($this->debug) {
// Header("Content-type: text/html");
} else {
Header("Content-type: application/pdf");
// Header("Content-disposition: attachment; filename=\"report.pdf\"");
}
}
//DebugPrint wrapper..Only prints when debug==1
function DebugPrint($message) {
if (!$this->debug)
return;
print "<font size=2>".htmlentities($message)."</font><br/>\n";
}
//Debug Assignments
function DebugAssign() {
if (!$this->debug)
return;
print "<font size=2><pre>\nGlobal\n";
print_r($this->subs);
foreach ($this->blocksubs as $key=>$val) {
print "Block: $key\n";
print_r($val);
}
print "</pre></font>";
}
//Assign variables
// ie. Assign(array('key'=>'val'));
// or Assign("key","val");
function Assign($key,$val=NULL) {
if (is_array($key)) {
foreach($key as $t_key=>$t_val) {
$this->DebugPrint("Assigning $t_key=>$t_val");
$this->cmnAssign(&$this->subs["keys"],&$this->subs["vals"],$t_key,$t_val);
}
} else {
$this->DebugPrint("Assigning $key=>$val");
$this->cmnAssign(&$this->subs["keys"],&$this->subs["vals"],$key,$val);
}
}
// $block is the name associated with the block.
// ie. AssignBlock("block",array('key'=>'val'));
// or AssignBlock("block","key","val");
function AssignBlock($block,$key,$val=NULL) {
if (!isset($this->blocksubs[$block])) {
$this->blocksubs[$block]["keys"][0]=array();
$this->blocksubs[$block]["vals"][0]=array();
$this->blocksubs[$block]["current"]=0;
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
"DEFAULTS.BLOCK",$block);
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
"DEFAULTS.ITERATION",1);
}
if (is_array($key)) { //assiciative array passed
foreach($key as $t_key=>$t_val) {
$this->DebugPrint("Assigning \"$block\" $t_key=>$t_val");
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
".$t_key",$t_val);
}
} else { //plain old strings were passed
$this->DebugPrint("Assigning \"$block\" $key=>$val");
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
".$key",$val);
}
}
//incremenmnt to next block iteration
function NextBlock($block) {
if (isset($this->blocksubs[$block])) {
$this->blocksubs[$block]["keys"][++$this->blocksubs[$block]["current"]]=array();
$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]]=array();
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
"DEFAULTS.BLOCK",$block);
$this->cmnAssign(
&$this->blocksubs[$block]["keys"][$this->blocksubs[$block]["current"]],
&$this->blocksubs[$block]["vals"][$this->blocksubs[$block]["current"]],
"DEFAULTS.ITERATION",$this->blocksubs[$block]["current"]+1);
}
}
//Close PDF and finish flushing output to client
function Close() {
if ($this->debug)
ob_start();
PDF_close($this->pdf);
if ($this->debug)
ob_end_clean();
}
//Parse through the XML once document and generate PDF (can be called multiple times)
//Returns XML Error messages if bad XML. otherwise false.
function Parse() {
$this->DebugAssign();
$error = XMLParseFile (&$this->parser, "pdf.xml", 0, "", 1,"UTF-8");
if (strcmp ($error, "")) {
return $error;
} else {
$this->WalkXML ("0"); //&$this->parser->structure, &$this->parser->positions);
return false;
}
}
//END OF use Public functions.. The rest are "internal use only" functions
//Recursive funtion to "WALK" the XML tree and call rendering routines
function WalkXML ($path) {
if (is_array($this->parser->structure[$path])) {
//Beginning Tag
$this->startElement($path);
//Check for looping
$count=1;
if (strcmp ($this->parser->structure[$path]["Tag"], "BLOCK") == 0) {
$this->page["block"]["name"]=$this->parser->structure[$path]["Attributes"]["NAME"];
for ($this->page["block"]["current"]=0;
$this->page["block"]["current"]<=$this->blocksubs[$this->page["block"]["name"]]["current"];
$this->page["block"]["current"]++) {
for ($element = 0; $element < $this->parser->structure[$path]["Elements"];$element++)
$this->WalkXML($path.",$element");
}
$this->page["block"]["name"]=""; //Clear out block;
} else { //not in a block. Normal parsing
//walk sub tags/content
for ($element = 0; $element < $this->parser->structure[$path]["Elements"];$element++)
$this->WalkXML($path.",$element");
}
//End Tag
$this->endElement($path);
} else {
//Content
//Find parent path
$parentpath = substr($path,0,strrpos($path,","));
$this->DebugPrint("PATH=".$path."-".strrpos($path,",")."-".$parentpath);
$data = trim(preg_replace("/(\ +|\n)/"," ",$this->parser->structure[$path]));
if (strlen($data)>0) {
$this->characterData($this->parser->structure[$parentpath]["Tag"]
,$this->parser->structure[$parentpath]["Attributes"]
,$data);
}
}
}
//set a font. $index of the font in the font stack (-1 for latest). if $force is true then
// this font's height will be forced for line height otherwise the max of the current
// lineheight and the fonts will be used.
function setfont($index=-1,$force=false) {
if ($index<0) {
$index = count($this->fontstack["Font"])-1;
}
//line height
if ($force) {
$this->page["fontlead"] = $this->fontstack["Size"][$index]*1.5;
} else {
$this->page["fontlead"] = max($this->page["fontlead"],$this->fontstack["Size"][$index]*1.5);
}
$this->DebugPrint ("fontlead=".$this->page["fontlead"]);
PDF_set_font($this->pdf,$this->fontstack["Font"][$index],$this->fontstack["Size"][$index],"host");
}
//Add font to the font stack $fontname is the name of the registered font. $size is the point size
//if either parameter is null("" or 0) the previous fonts value will be used.
function addfont($fontname="",$size=0) {
array_push($this->fontstack["Font"],(
($fontname=="")
? $this->fontstack["Font"][count($this->fontstack["Size"])-1]
: $fontname)
);
array_push($this->fontstack["Size"],(
($size<1)
? $this->fontstack["Size"][count($this->fontstack["Size"])-1]
: $size)
);
return count($this->fontstack["Font"])-1;
}
//returns an indexed array of the Font name and Font size of the specified font in the index
function getfont($index=-1) {
if ($index<0) {
$index = count($this->fontstack["Font"])-1;
}
return array($this->fontstack["Font"][$index],$this->fontstack["Size"][$index]);
}
//removes the last font from the font stack
function removefont() {
array_pop($this->fontstack["Font"]);
array_pop($this->fontstack["Size"]);
}
//finds the bold version of the current (or specified) font and returns the name of the font
function getbold($index=-1) {
$font = $this->getfont($index);
$bolds = array(
'Helvetica'=>"Helvetica-Bold",
'Courier'=>"Courier-Bold",
'Times-Roman'=>"Times-Roman-Bold",
'Helvetica-Oblique'=>"Helvetica-BoldOblique",
'Courier-Oblique'=>"Courier-BoldOblique",
'Time-Roman-Italic'=>"Times-Roman-BoldItalic",
);
return (empty($bolds[$font[0]])?"":$bolds[$font[0]]);
}
//finds the italic version of the current (or specified) font and returns the name of the font
function getitalic($index=-1) {
$font = $this->getfont($index);
$bolds = array(
'Helvetica'=>"Helvetica-Oblique",
'Courier'=>"Courier-Oblique",
'Times-Roman'=>"Times-Roman-Italic",
'Helvetica-Bold'=>"Helvetica-BoldOblique",
'Courier-Bold'=>"Courier-BoldOblique",
'Time-Roman-Bold'=>"Times-Roman-BoldItalic",
);
return (empty($bolds[$font[0]])?"":$bolds[$font[0]]);
}
//add (and replace) key/value pair in substitution arrays
function cmnAssign(&$keys,&$vals,$key,$val) {
if ( ($i=array_search("{".$key."}",$keys))!==NULL) {
$this->DebugPrint("previous=".$i);
$keys[$i] = "{".$key."}";
$vals[$i] = $val;
} else {
array_push($keys,"{".$key."}");
array_push($vals,$val);
}
}
//Ends the last page
function cmnEndPage() {
if (!$this->page["closed"]) {
if($this->debug)
ob_start();
PDF_end_page($this->pdf);
if ($this->debug)
ob_end_clean();
$this->page["closed"]=true;
}
}
//Ends last page (if one exists) and Begins a new page and resets curX and curY. sets font info.
function cmnNewPage() {
$this->cmnEndPage(); //End previous page if exists
//Current Position of cursor
$this->page["curY"] = $this->page["height"]-$this->page["margin"]; //Current Y position
$this->page["curX"] = $this->page["margin"]; //Current X position
//Begin the page
PDF_begin_page($this->pdf,$this->page["width"],$this->page["height"]);
$this->page["closed"]=false;
if ($this->page["ticks"]) {
$this->cmnDrawTicks();
} else {
$this->setfont();
}
}
//Draw ticks on sides of page
function cmnDrawTicks() {
//Generate debuging ticks;
$c=0;
PDF_set_font($this->pdf,"Helvetica",8,"host");
//left side
for ($i=$this->page["height"]; $i>0;$i-=10) {
if (!($c % 5)) {
PDF_show_xy($this->pdf,$this->page["height"]-$i,10,$i-3);
}
PDF_moveto($this->pdf,0,$i);
if (!($c % 5)) {
PDF_lineto($this->pdf,10,$i);
} else {
PDF_lineto($this->pdf,7,$i);
}
PDF_stroke($this->pdf);
$c++;
}
//top edge
$c=0;
for ($i=0; $i<$this->page["width"]; $i+=10) {
if (!($c % 5)) {
PDF_show_xy($this->pdf,$i,$i-6,$this->page["height"]-18);
}
PDF_moveto($this->pdf,$i,$this->page["height"]);
if (!($c % 5)) {
PDF_lineto($this->pdf,$i,$this->page["height"]-7);
} else {
PDF_lineto($this->pdf,$i,$this->page["height"]-10);
}
PDF_stroke($this->pdf);
$c++;
}
$this->setfont();
}
//handles the "beginning" of a tag and sets parameters appropriatly
function startElement($path) {
$attribs = &$this->parser->structure[$path]["Attributes"];
switch ($this->parser->structure[$path]["Tag"]) {
case 'PDF':
$this->DebugPrint("Begining of file");
$this->page["closed"]=true;
break;
case 'PAGE':
$this->page["fontlead"]=0;
$this->page["tab"] = 36; //A single 'tab'. For paragraph formatting.
//Margin spacing Default = 36 (half inch);
$this->page["margin"]=(empty($attribs["MARGIN"])?36:$attribs["MARGIN"]*72);
//Default Width
$this->page["width"]=(empty($attribs["WIDTH"])?612:$attribs["WIDTH"]*72);
//Default Height
$this->page["height"]=(empty($attribs["HEIGHT"])?792:$attribs["HEIGHT"]*72);
$this->page["ticks"]=($attribs["TICKS"]==1?true:false);
//Make new page
$this->cmnNewPage();
if (!empty($attribs["TOPMARGIN"])) {
$this->page["curY"]=$this->page["height"]-$attribs["TOPMARGIN"]*72;
}
break;
case 'PARA':
if (!empty($attribs["INDENT"])) {
$this->page["curX"]+=$this->page["tab"];
}
if (!empty($attribs["TAB"])) {
$this->page["left"]=$this->page["margin"]+$attribs["TAB"]*72;
$this->page["curX"]+=$attribs["TAB"]*72;
} else {
$this->page["left"]=$this->page["margin"];
}
$this->page["align"]=(empty($attribs["ALIGN"])?"left":strtolower($attribs["ALIGN"]));
break;
case 'BOLD':
$this->setfont($this->addfont($this->getbold()));
break;
case 'ITALIC':
$this->setfont($this->addfont($this->getitalic()));
break;
case 'FONTDEF':
$this->DebugPrint(
"face=".$attribs["FACE"]
." afm=".$attribs["AFM"]
." pfm=".$attribs["PFM"]
." outline=".$attribs["OUTLINE"]);
PDF_set_parameter($this->pdf,"FontAFM",$attribs["FACE"]."=".$attribs["AFM"]);
PDF_set_parameter($this->pdf,"FontOutline",$attribs["FACE"]."=".$attribs["OUTLINE"]);
break;
case 'FONT':
$this->DebugPrint("face=".$attribs["FACE"]." size=".$attribs["SIZE"]);
$this->setfont($this->addfont($attribs["FACE"],$attribs["SIZE"]));
break;
case 'TAB':
$this->page["curX"]=72 * $attribs["TAB"];
break;
case 'BR':
$this->page["curX"]=$this->page["left"];
$this->page["curY"]-=$this->page["fontlead"];
break;
case 'BREAK':
$this->cmnNewPage();
break;
default:
break;
}
}
//handles the "end" of a tag and (un)sets parameters appropriatly
function endElement($path) {
$attribs = &$this->parser->structure[$path]["Attributes"];
switch ($this->parser->structure[$path]["Tag"]) {
case 'PDF':
$this->DebugPrint("End of document");
$this->cmnEndPage();
break;
case 'PAGE':
//End the page and push to client.
// PDF_end_page($this->pdf);
break;
case 'PARA':
$this->page["curY"]-=$this->page["fontlead"]; //goto next line.
$this->setfont(-1,true); //reset font and fontlead (line height)
$this->page["curX"]=$this->page["margin"]; //Back to the left of the line
if (empty($attribs["NOBREAK"])) {
$this->page["curY"]-=$this->page["fontlead"]; //insert space between paragraphs
}
//$this->setfont(); //Set Default Font
break;
case 'FONT':
case 'ITALIC':
case 'BOLD':
$this->removefont(); //Remove Last Font
$this->setfont(); //Set Previous Font
break;
default:
break;
}
} //end Element
//handles the data between tags (the actual text)
function characterData($tag,$attribs,$data) {
$this->DebugPrint("CharData tag=$tag");
switch ($tag) {
case 'FONT': //Displays text in different font. Formatting done in beginElement
case 'BOLD': //Displays bold text. Formating done in beginElement
case 'ITALIC': //Displays italic text. Formating done in beginElement
//case 'LOOP': //incase loop tag around plain text in a para. (not preferred)
case 'NOSUB': //Do not substitute variables
case 'PARA': //Displays a paragraph
//Substitute variables
if (strcmp($tag,'NOSUB')<>0) {
$data = str_replace($this->subs["keys"],$this->subs["vals"],$data);
if (!empty($this->page["block"]["name"])) {
$this->DebugPrint("blocksub=".$this->page["block"]["name"]."-".$this->page["block"]["current"]);
$data = str_replace(
$this->blocksubs[$this->page["block"]["name"]]["keys"][$this->page["block"]["current"]],
$this->blocksubs[$this->page["block"]["name"]]["vals"][$this->page["block"]["current"]],
$data);
}
}
while (strlen($data)>0) {
//Check to see if we are past the bottom of the page.
// It's backward login due to PDF's 0 coord is at the bottom
if ($this->page["curY"] < ($this->page["margin"]+$this->page["fontlead"]) ) {
$this->DebugPrint("Ending Current Page");
$this->cmnNewPage();
$this->DebugPrint("Starting New Page");
}
$extra = PDF_show_boxed($this->pdf,$data,
$this->page["curX"], //Left
$this->page["curY"]-$this->page["fontlead"], //Top
$this->page["width"]-$this->page["curX"]-$this->page["margin"], //Width
$this->page["fontlead"], //Height (one line)
$this->page["align"]); //Justification
$this->DebugPrint(
"X=".$this->page["curX"]
." Y=".$this->page["curY"]
." extra=".$extra
." end=".PDF_get_value($this->pdf,"textx")
." W=".PDF_stringwidth($this->pdf,$data));
$this->DebugPrint("text=".$data);
if (PDF_stringwidth($this->pdf,$data)
> ($this->page["width"]-$this->page["margin"]-$this->page["curX"])) { //Text wider than page
$this->page["curX"]=$this->page["left"]; //go back to beginning of line
$this->page["curY"]-=$this->page["fontlead"]; //Go down one line
$this->setfont(-1,true); //Reset line height (also resets fontlead)
} else { //fits on one line get current "X" position
$this->page["curX"]=PDF_get_value($this->pdf,"textx");
}
//update $data with unprinted text
//if ($extra>0) {
$data = substr($data,(strlen($data)-$extra));
//};
} //while
break;
default:
break;
}
}
} //End Class PDFTemplate
?>