Zend_Pdf calculating length of text string in current font for line wrapping
Asked Answered
U

2

12

Does anyone have an easy way to calculate how many points across a page a piece of text will consume in a particular font and size? (easy = minimal lines of code + computationally cheap). Zend_Pdf doesn't appear to have a function that does this, except for some very expensive calls for each character to getGlyphForCharacter(), getUnitsPerEm() and getWidthsForGlyph().

I'm generating a multi page PDF with several tables on each page, and need to wrap the text within the columns. It's already taking a few seconds to create it, and I don't want it to take too much longer or I'll have to start messing around with background tasks or progress bars etc.

The only solution I came up with is pre-computing the width (in points) of each character in each font size used, then adding these up over each string. Still quite costly.

Am I missing something? Or have you got anything simpler?

thanks!

Unrobe answered 16/8, 2009 at 5:56 Comment(0)
B
29

There's a way to calculate widths exactly, rather than using Gorilla3D's worst case algorithm.

Try this code from http://devzone.zend.com/article/2525-Zend_Pdf-tutorial#comments-2535

I've used it in my application to calculate offsets for right-aligned text and it works

/**
* Returns the total width in points of the string using the specified font and
* size.
*
* This is not the most efficient way to perform this calculation. I'm
* concentrating optimization efforts on the upcoming layout manager class.
* Similar calculations exist inside the layout manager class, but widths are
* generally calculated only after determining line fragments.
* 
* @link http://devzone.zend.com/article/2525-Zend_Pdf-tutorial#comments-2535 
* @param string $string
* @param Zend_Pdf_Resource_Font $font
* @param float $fontSize Font size in points
* @return float
*/
function widthForStringUsingFontSize($string, $font, $fontSize)
{
     $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string);
     $characters = array();
     for ($i = 0; $i < strlen($drawingString); $i++) {
         $characters[] = (ord($drawingString[$i++]) << 8 ) | ord($drawingString[$i]);
     }
     $glyphs = $font->glyphNumbersForCharacters($characters);
     $widths = $font->widthsForGlyphs($glyphs);
     $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize;
     return $stringWidth;
 }

With regard to performance, I haven't used this intensively in a script but I can imagine it's slow. I'd suggest writing the PDFs to disk, if possible, so repeat views are very fast, and caching/hard coding data where possible.

Benefactor answered 16/8, 2009 at 22:49 Comment(2)
I've used this too. Works as advertised.Sweepback
that looks good. I've got a fixed vocabulary of about 200 phrases, so could use the above code (slightly adapted) to precompute and cache the word wrapped strings. The pdf is generated only once (at first viewing) and then cached as well. This should result in almost no performance penalty and some much nicer formatting. Thanks for your help!Unrobe
M
0

Thinking over this a bit more. Take the widest Glyphs of the font your using and based that as the width for each character. It wont be accurate but it will prevent pushing text past the mark.

$pdf = new Zend_Pdf();
$font      = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER); 
$font_size = $pdf->getFontSize();


$letters = array();
foreach(range(0, 127) as $idx)
{
    array_push($letters, chr($idx));
}
$max_width = max($font->widthsForGlyphs($letters));

// Text wrapping settings
$text_font_size = $max_width; // widest possible glyph
$text_max_width = 238;        // 238px

// Text wrapping calcs
$posible_character_limit = round($text_max_width / $text_font_size);
$text = wordwrap($text, $posible_character_limit, "@newline@");
$text = explode('@newline@', $text);
Massarelli answered 16/8, 2009 at 6:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.