Add-On Flowing block
Informations
Author:Damon KohlerLicense: GPL
Description
This script allows writing text in a block in much the same way the Write function works. The block is defined in a manner similar to MultiCell (width, line height, border, alignment and filling color). It is possible to add content with WriteFlowingBlock and change font attributes (SetFont and SetFontSize) between two calls.Changes in text color are not supported; however, such a change could be easily added.
This was written as part of the phpFOP project.
Source
<?php
require('fpdf.php');
class PDF_FlowingBlock extends FPDF
{
protected $flowingBlockAttr;
function saveFont()
{
$saved = array();
$saved[ 'family' ] = $this->FontFamily;
$saved[ 'style' ] = $this->FontStyle;
$saved[ 'sizePt' ] = $this->FontSizePt;
$saved[ 'size' ] = $this->FontSize;
$saved[ 'curr' ] = $this->CurrentFont;
return $saved;
}
function restoreFont( $saved )
{
$this->FontFamily = $saved[ 'family' ];
$this->FontStyle = $saved[ 'style' ];
$this->FontSizePt = $saved[ 'sizePt' ];
$this->FontSize = $saved[ 'size' ];
$this->CurrentFont = $saved[ 'curr' ];
if( $this->page > 0)
$this->_out( sprintf( 'BT /F%d %.2F Tf ET', $this->CurrentFont[ 'i' ], $this->FontSizePt ) );
}
function newFlowingBlock( $w, $h, $b = 0, $a = 'J', $f = 0 )
{
// cell width in points
$this->flowingBlockAttr[ 'width' ] = $w * $this->k;
// line height in user units
$this->flowingBlockAttr[ 'height' ] = $h;
$this->flowingBlockAttr[ 'lineCount' ] = 0;
$this->flowingBlockAttr[ 'border' ] = $b;
$this->flowingBlockAttr[ 'align' ] = $a;
$this->flowingBlockAttr[ 'fill' ] = $f;
$this->flowingBlockAttr[ 'font' ] = array();
$this->flowingBlockAttr[ 'content' ] = array();
$this->flowingBlockAttr[ 'contentWidth' ] = 0;
}
function finishFlowingBlock()
{
$maxWidth = $this->flowingBlockAttr[ 'width' ];
$lineHeight = $this->flowingBlockAttr[ 'height' ];
$border = $this->flowingBlockAttr[ 'border' ];
$align = $this->flowingBlockAttr[ 'align' ];
$fill = $this->flowingBlockAttr[ 'fill' ];
$content = $this->flowingBlockAttr[ 'content' ];
$font = $this->flowingBlockAttr[ 'font' ];
// set normal spacing
$this->_out( sprintf( '%.3F Tw', 0 ) );
// print out each chunk
// the amount of space taken up so far in user units
$usedWidth = 0;
foreach ( $content as $k => $chunk )
{
$b = '';
if ( is_int( strpos( $border, 'B' ) ) )
$b .= 'B';
if ( $k == 0 && is_int( strpos( $border, 'L' ) ) )
$b .= 'L';
if ( $k == count( $content ) - 1 && is_int( strpos( $border, 'R' ) ) )
$b .= 'R';
$this->restoreFont( $font[ $k ] );
// if it's the last chunk of this line, move to the next line after
if ( $k == count( $content ) - 1 )
$this->Cell( ( $maxWidth / $this->k ) - $usedWidth + 2 * $this->cMargin, $lineHeight, $chunk, $b, 1, $align, $fill );
else
$this->Cell( $this->GetStringWidth( $chunk ), $lineHeight, $chunk, $b, 0, $align, $fill );
$usedWidth += $this->GetStringWidth( $chunk );
}
}
function WriteFlowingBlock( $s )
{
// width of all the content so far in points
$contentWidth =& $this->flowingBlockAttr[ 'contentWidth' ];
// cell width in points
$maxWidth = $this->flowingBlockAttr[ 'width' ];
$lineCount =& $this->flowingBlockAttr[ 'lineCount' ];
// line height in user units
$lineHeight = $this->flowingBlockAttr[ 'height' ];
$border = $this->flowingBlockAttr[ 'border' ];
$align = $this->flowingBlockAttr[ 'align' ];
$fill = $this->flowingBlockAttr[ 'fill' ];
$content =& $this->flowingBlockAttr[ 'content' ];
$font =& $this->flowingBlockAttr[ 'font' ];
$font[] = $this->saveFont();
$content[] = '';
$currContent =& $content[ count( $content ) - 1 ];
// where the line should be cutoff if it is to be justified
$cutoffWidth = $contentWidth;
// for every character in the string
for ( $i = 0; $i < strlen( $s ); $i++ )
{
// extract the current character
$c = $s[ $i ];
// get the width of the character in points
$cw = $this->CurrentFont[ 'cw' ][ $c ] * ( $this->FontSizePt / 1000 );
if ( $c == ' ' )
{
$currContent .= ' ';
$cutoffWidth = $contentWidth;
$contentWidth += $cw;
continue;
}
// try adding another char
if ( $contentWidth + $cw > $maxWidth )
{
// won't fit, output what we have
$lineCount++;
// contains any content that didn't make it into this print
$savedContent = '';
$savedFont = array();
// first, cut off and save any partial words at the end of the string
$words = explode( ' ', $currContent );
// if it looks like we didn't finish any words for this chunk
if ( count( $words ) == 1 )
{
// save and crop off the content currently on the stack
$savedContent = array_pop( $content );
$savedFont = array_pop( $font );
// trim any trailing spaces off the last bit of content
$currContent =& $content[ count( $content ) - 1 ];
$currContent = rtrim( $currContent );
}
// otherwise, we need to find which bit to cut off
else
{
$lastContent = '';
for ( $w = 0; $w < count( $words ) - 1; $w++)
$lastContent .= "{$words[ $w ]} ";
$savedContent = $words[ count( $words ) - 1 ];
$savedFont = $this->saveFont();
// replace the current content with the cropped version
$currContent = rtrim( $lastContent );
}
// update $contentWidth and $cutoffWidth since they changed with cropping
$contentWidth = 0;
foreach ( $content as $k => $chunk )
{
$this->restoreFont( $font[ $k ] );
$contentWidth += $this->GetStringWidth( $chunk ) * $this->k;
}
$cutoffWidth = $contentWidth;
// if it's justified, we need to find the char spacing
if( $align == 'J' )
{
// count how many spaces there are in the entire content string
$numSpaces = 0;
foreach ( $content as $chunk )
$numSpaces += substr_count( $chunk, ' ' );
// if there's more than one space, find word spacing in points
if ( $numSpaces > 0 )
$this->ws = ( $maxWidth - $cutoffWidth ) / $numSpaces;
else
$this->ws = 0;
$this->_out( sprintf( '%.3F Tw', $this->ws ) );
}
// otherwise, we want normal spacing
else
$this->_out( sprintf( '%.3F Tw', 0 ) );
// print out each chunk
$usedWidth = 0;
foreach ( $content as $k => $chunk )
{
$this->restoreFont( $font[ $k ] );
$stringWidth = $this->GetStringWidth( $chunk ) + ( $this->ws * substr_count( $chunk, ' ' ) / $this->k );
// determine which borders should be used
$b = '';
if ( $lineCount == 1 && is_int( strpos( $border, 'T' ) ) )
$b .= 'T';
if ( $k == 0 && is_int( strpos( $border, 'L' ) ) )
$b .= 'L';
if ( $k == count( $content ) - 1 && is_int( strpos( $border, 'R' ) ) )
$b .= 'R';
// if it's the last chunk of this line, move to the next line after
if ( $k == count( $content ) - 1 )
$this->Cell( ( $maxWidth / $this->k ) - $usedWidth + 2 * $this->cMargin, $lineHeight, $chunk, $b, 1, $align, $fill );
else
{
$this->Cell( $stringWidth + 2 * $this->cMargin, $lineHeight, $chunk, $b, 0, $align, $fill );
$this->x -= 2 * $this->cMargin;
}
$usedWidth += $stringWidth;
}
// move on to the next line, reset variables, tack on saved content and current char
$this->restoreFont( $savedFont );
$font = array( $savedFont );
$content = array( $savedContent . $s[ $i ] );
$currContent =& $content[ 0 ];
$contentWidth = $this->GetStringWidth( $currContent ) * $this->k;
$cutoffWidth = $contentWidth;
}
// another character will fit, so add it on
else
{
$contentWidth += $cw;
$currContent .= $s[ $i ];
}
}
}
}
?>
Example
This example outputs the same text with the four possible alignments (justified, left-aligned, right-aligned and centered).<?php
require('flowing_block.php');
$pdf = new PDF_FlowingBlock();
$pdf->AddPage();
$pdf->newFlowingBlock( 40, 6, 'TBLR', 'J' );
$pdf->SetFont( 'Arial', 'B', 16 );
$pdf->WriteFlowingBlock( 'Hello ' );
$pdf->SetFont( 'Arial', 'I', 8 );
$pdf->WriteFlowingBlock( 'World! ' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( 'This is a test of the flowing block script.' );
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->WriteFlowingBlock( ' All' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( ' of this should be justified correctly.' . str_repeat( ' This is a test of the flowing block script.', 3 ) );
$pdf->finishFlowingBlock();
$pdf->AddPage();
$pdf->newFlowingBlock( 40, 6, 'TBLR', 'L' );
$pdf->SetFont( 'Arial', 'B', 16 );
$pdf->WriteFlowingBlock( 'Hello ' );
$pdf->SetFont( 'Arial', 'I', 8 );
$pdf->WriteFlowingBlock( 'World! ' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( 'This is a test of the flowing block script.' );
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->WriteFlowingBlock( ' All' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( ' of this should be justified correctly.' . str_repeat( ' This is a test of the flowing block script.', 3 ) );
$pdf->finishFlowingBlock();
$pdf->AddPage();
$pdf->newFlowingBlock( 40, 6, 'TBLR', 'R' );
$pdf->SetFont( 'Arial', 'B', 16 );
$pdf->WriteFlowingBlock( 'Hello ' );
$pdf->SetFont( 'Arial', 'I', 8 );
$pdf->WriteFlowingBlock( 'World! ' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( 'This is a test of the flowing block script.' );
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->WriteFlowingBlock( ' All' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( ' of this should be justified correctly.' . str_repeat( ' This is a test of the flowing block script.', 3 ) );
$pdf->finishFlowingBlock();
$pdf->AddPage();
$pdf->newFlowingBlock( 40, 6, 'TBLR', 'C' );
$pdf->SetFont( 'Arial', 'B', 16 );
$pdf->WriteFlowingBlock( 'Hello ' );
$pdf->SetFont( 'Arial', 'I', 8 );
$pdf->WriteFlowingBlock( 'World! ' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( 'This is a test of the flowing block script.' );
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->WriteFlowingBlock( ' All' );
$pdf->SetFont( 'Times', '', 10 );
$pdf->WriteFlowingBlock( ' of this should be justified correctly.' . str_repeat( ' This is a test of the flowing block script.', 3 ) );
$pdf->finishFlowingBlock();
$pdf->Output();
?>