By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
440,086 Members | 1,442 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 440,086 IT Pros & Developers. It's quick & easy.

width of a charactor

P: n/a
In php, is there a way obtain the width of a charactor of a certain font?

Alex

--
==================================================
Cell Phone Batteries at 30-50%+ off retail prices!
http://www.pocellular.com
==================================================
Jul 17 '05 #1
Share this Question
Share on Google+
2 Replies


P: n/a
"Alex Shi" <ch****@stonix.com> wrote in message
news:F8******************@nntp-post.primus.ca...
In php, is there a way obtain the width of a charactor of a certain font?

Alex

--
==================================================
Cell Phone Batteries at 30-50%+ off retail prices!
http://www.pocellular.com
==================================================


yes/no

Depends on where this chr is destined, if you are working with images, yes,
if you are working with web pages no. But if you need to set chr width, do
it in CSS.

--
Mike Bradley
http://www.gzentools.com -- free online php tools
Jul 17 '05 #2

P: n/a
Uzytkownik "Alex Shi" <ch****@stonix.com> napisal w wiadomosci
news:F8******************@nntp-post.primus.ca...
In php, is there a way obtain the width of a charactor of a certain font?

Alex

--
==================================================
Cell Phone Batteries at 30-50%+ off retail prices!
http://www.pocellular.com
==================================================


Yes, but it's rather painful. With Type 1 fonts it's easier, as the width
info is stored in a plain text file. With TrueType fonts it's quite hard.

Look at the PDF functions and see if there's a function that yields the
width of a string. Some time ago I've written a PDF layout engine in PHP.
Exactly how it works I can't remember. There's a PDFTrueTypeFont class in
there. The GetTextWidth method should give you the width of a text string,
while GetCharWidth method--the width of a char.

[begin code flood]

<?php

$TTFDirectory = false;
$Type1Directory = false;
$imageDirectory = false;
$charMapDirectory = false;

class PDFObj {
var $id;
var $offset;

function SetId($id) {
$this->id = $id;
}

function SetOffset($pos) {
$this->offset = $pos;
}

function Registered() {
return $this->id ? True : False;
}

function Reference() {
return "$this->id 0 R";
}

function Written() {
return $this->offset ? True : False;
}

function Output(&$pdf_file) {
$pdf_file->Write("$this->id 0 obj\n");
$pdf_file->Write($this->ToString());
$pdf_file->Write("\nendobj\n");
}
}

class PDFBoolean extends PDFObj {
var $value;

function PDFBoolean($value) {
$this->value = $value;
}

function ToString() {
return ($this->value) ? 'true' : 'false';
}
}

class PDFInteger extends PDFObj {
var $value;

function PDFInteger($value) {
$this->value = $value;
}

function Add($value) {
$this->value += $value;
}

function ToString() {
return (string) $this->value;
}
}

class PDFHex extends PDFInteger {

function PDFHex($value) {
$this->PDFInteger($value);
}

function ToString() {
if($this->value < 256) $format = '<%02x>';
else if($this->value < 65536) $format = '<%04x>';
else $format = '<%08x>';
return sprintf($format, $this->value);
}
}

$string_escape_table = array('(' => '\\(', ')' => '\\)', '\\' => '\\\\',
"\n" => '\n', "\r" => '\r', "\t" => '\t');

class PDFString extends PDFObj {
var $value;

function PDFString($value) {
$this->value = $value;
}

function Prepend($obj) {
$this->value = ((get_class($obj) == 'pdfstring') ? $obj->value : $obj) .
$this->value;
}

function Append($obj) {
$this->value .= ((get_class($obj) == 'pdfstring') ? $obj->value : $obj);
}

function IsEmpty() {
return (strlen($this->value) == 0);
}

function ToString() {
global $string_escape_table;
return '(' . strtr($this->value, $string_escape_table) . ')';
}
}

class PDFName extends PDFObj {
var $value;

function PDFName($value) {
$this->value = $value;
}

function ToString() {
return "/$this->value";
}
}

function Name($name) {
return new PDFName($name);
}

class PDFCollection extends PDFObj {

function NewObj($value) {
if(is_numeric($value))
{
return new PDFInteger($value);
}
else if(is_string($value))
{
return new PDFString($value);
}
else if(is_array($value))
{
if(sizeof($value) > 0 && !isset($value[0])) // is associative array
{
$new_array = new PDFDictionary();
foreach($value as $akey => $avalue)
{
if(is_object($avalue))
{
$new_array->AssignObj($akey, $value[$akey]);
}
else
{
$new_array->Assign($akey, $avalue);
}
}
}
else
{
$new_array = new PDFArray();
foreach($value as $akey => $avalue)
{
if(is_object($avalue))
{
$new_array->AddObj($value[$akey]);
}
else
{
$new_array->Add($avalue);
}
}
}
return $new_array;
}
else
{
return new PDFBoolean($value);
}
}
}

class PDFDictionary extends PDFCollection {
var $values;

function PDFDictionary($type = false) {
$this->values = array();
if($type)
{
$this->values['Type'] = new PDFName($type);
}
}

function Assign($key, $value) {
if($value !== False)
{
if(is_object($value))
{
$this->values[$key] = $value;
}
else
{
$this->values[$key] = PDFCollection::NewObj($value);
}
}
}

function AssignObj($key, &$obj) {
if(!is_object($obj))
{
die("$key not an object!");
}
$this->values[$key] = &$obj;
}

function IsAssigned($key) {
return isset($this->values[$key]);
}

function Unassign($key) {
unset($this->values[$key]);
}

function &Object($key) {
return $this->values[$key];
}

function ToString() {
$s = '<<';
foreach($this->values as $key => $obj)
{
$s .= " /$key ";
if($obj->Registered())
{
$s .= $obj->Reference();
}
else
{
$s .= $obj->ToString($value);
}
}
$s .= ' >>';
return $s;
}
}

class PDFArray extends PDFCollection {
var $values;

function PDFArray() {
$this->values = array();
}

function Count() {
return sizeof($this->values);
}

function Add($value) {
if($value !== False)
{
$index = count($this->values);
$this->values[] = PDFCollection::NewObj($value);
return $index;
}
}

function AddObj(&$obj) {
$index = count($this->values);
$this->values[] = &$obj;
return $index;
}

function InsertObj(&$obj, $index) {
array_splice($this->values, $index, 0, array(0));
$this->values[$index] = &$obj;
return $index;
}

function IsEmpty() {
return (sizeof($this->values) == 0);
}

function ToString() {
$s = '[';
foreach($this->values as $obj)
{
$s .= ' ';
if($obj->Registered())
{
$s .= $obj->Reference();
}
else
{
$s .= $obj->ToString();
}
}
$s .= ' ]';
return $s;
}
}

class PDFStream extends PDFDictionary {
var $data;

function PDFStream($type = false) {
$this->PDFDictionary($type);
}

function Write($s) {
$this->data .= $s;
}

function Output(&$pdf_file) {
$this->Assign('Length', strlen($this->data));
$pdf_file->Write("$this->id 0 obj\n");
$pdf_file->Write($this->ToString());
$pdf_file->Write("\nstream\n");
$pdf_file->Write($this->data);
$pdf_file->Write("endstream");
$pdf_file->Write("\nendobj\n");
}
}

class PDFFileStream extends PDFStream {

function PDFFileStream($filePath) {
$this->PDFStream();
$filesize = filesize($filePath);
$file = fopen($filePath, "rb");
$this->data = fread($file, $filesize);
fclose($file);
}
}

if(function_exists('gzdeflate'))
{

class PDFFlateStream extends PDFStream {
var $data;

function PDFFlateStream() {
$this->PDFStream();
$this->Assign('Filter', Name('FlateDecode'));
}

function Output(&$pdf_file) {
$deflated_data = gzdeflate($this->data);
$length = strlen($deflated_data) + 2;
$this->Assign('Length', $length);
$pdf_file->Write("$this->id 0 obj\n");
$pdf_file->Write($this->ToString());
$pdf_file->Write("\nstream\n");
$pdf_file->Write("H");
$pdf_file->Write($deflated_data);
$pdf_file->Write("endstream");
$pdf_file->Write("\nendobj\n");
}
}

class PDFFlateFileStream extends PDFFlateStream {

function PDFFlateFileStream($filePath) {
$this->PDFFlateStream();
$filesize = filesize($filePath);
$file = fopen($filePath, "rb");
$this->data = fread($file, $filesize);
fclose($file);
}
}

}
else
{

class PDFFlateStream extends PDFStream {
var $data;
var $temp_file;
var $temp_filename;

function PDFFlateStream() {
$this->PDFStream();
$this->Assign('Filter', Name('FlateDecode'));
$this->temp_filename = tempnam('', 'strm');
$this->temp_file = fopen($this->temp_filename, "wb");
}

function Write($s) {
fwrite($this->temp_file, $s);
}

function Output(&$pdf_file) {
fclose($this->temp_file);
`gzip -9nf $this->temp_filename`;
$this->temp_filename .= '.gz';
$filesize = filesize($this->temp_filename);
$gz = fopen($this->temp_filename, 'rb');
fseek($gz, 10);
$deflated_data = fread($gz, $filesize - 14);
fclose($gz);
unlink($this->temp_filename);
$length = $filesize - 14 + 2;
$this->Assign('Length', $length);
$pdf_file->Write("$this->id 0 obj\n");
$pdf_file->Write($this->ToString());
$pdf_file->Write("\nstream\n");
$pdf_file->Write("H");
$pdf_file->Write($deflated_data);
$pdf_file->Write("endstream");
$pdf_file->Write("\nendobj\n");
}
}

class PDFFlateFileStream extends PDFStream {
var $filePath;

function PDFFlateFileStream($filePath) {
$this->PDFStream();
$this->filePath = $filePath;
}

function Output(&$pdf_file) {
$gzip_data = `gzip -9cnf $this->filePath`;
$deflated_data = substr($gzip_data, 10, -4);
$length = strlen($deflated_data) + 2;
$this->Assign('Length', $length);
$pdf_file->Write("$this->id 0 obj\n");
$pdf_file->Write($this->ToString());
$pdf_file->Write("\nstream\n");
$pdf_file->Write("H");
$pdf_file->Write($deflated_data);
$pdf_file->Write("endstream");
$pdf_file->Write("\nendobj\n");
}
}

}

class PDFTrueTypeFileStream extends PDFFlateFileStream {

function PDFTrueTypeFileStream($filePath) {
$this->PDFFlateFileStream($filePath);
$this->Assign('Length1', strlen($this->data));
}
}

class PDFType1FileStream extends PDFFileStream {

function PDFType1FileStream($filePath) {
$this->PDFFileStream($filePath);
$encrypted_start_index = strpos($this->data, "currentfile eexec\r") + 18;
$encrypted_end_index = strpos($this->data,
"0000000000000000000000000000000000000000000000000 000000000000000");
$this->Assign('Length1', $encrypted_start_index);
$this->Assign('Length2', $encrypted_end_index - $encrypted_start_index);
$this->Assign('Length3', strlen($this->data) - $encrypted_end_index);
}
}

class PDFRect extends PDFObj {
var $left;
var $top;
var $right;
var $bottom;
var $width;
var $height;

function PDFRect($left, $top, $right, $bottom) {
$this->left = (int) $left;
$this->top = (int) $top;
$this->right = (int) $right;
$this->bottom = (int) $bottom;
$this->width = $right - $left;
$this->height = $top - $bottom;
}

function ToString() {
return "[$this->left $this->top $this->right $this->bottom]";
}
}

class PDFDate extends PDFString {

function PDFDate($value = False) {
if(!$value) $value = time();
$this->PDFString('D:' . gmdate('YmdHis') . 'Z');
}
}

define('BLACK', 0);
define('WHITE', 1);
define('LIGHTGRAY', 0.9);
define('DARKGRAY', 0.4);

class PDFGray extends PDFObj {
var $level;

function PDFGray($level) {
$this->level = $level;
}

function ToStringStroking() {
return "$this->level G\n";
}

function ToStringNonStroking() {
return "$this->level g\n";
}
}

class PDFRGB extends PDFObj {
var $r;
var $g;
var $b;

function PDFRGB($r, $g, $b) {
$this->r = $r;
$this->g = $g;
$this->b = $b;
}

function ToStringStroking() {
return "$this->r $this->g $this->b RG\n";
}

function ToStringNonStroking() {
return "$this->r $this->g $this->b rg\n";
}
}

function ToUnicodeStr($s, $encoding) {
$map = GetCharSetUnicodeMap($encoding);
$us = "\xfe\xff";
for($i = 0, $len = strlen($s); $i < $len; $i++)
{
$c = ord($s[$i]);
$uc = $map[$c];
$us .= chr($uc >> 8) . chr($uc & 0x00FF);
}
return $us;
}

class PDFBookmark extends PDFDictionary {

function PDFBookmark(&$page, $title, $encoding = 'WinAnsiEncoding', $top =
false) {
$this->PDFDictionary();
$this->Assign('Title', ToUnicodeStr($title, $encoding));
$dest = ($top) ? array(&$page, Name('FitH'), $top) : array(&$page,
Name('Fit'));
$this->Assign('Dest', $dest);
}

function SetParent(&$parent) {
$this->Assign('Parent', $parent);
if(!$parent->IsAssigned('First'))
{
$parent->AssignObj('First', $this);
}
if($parent->IsAssigned('Last'))
{
$last =& $parent->Object('Last');
$last->AssignObj('Next', $this);
$this->AssignObj('Prev', $last);
}
$parent->AssignObj('Last', $this);
}
}

class PDFWebBookmark extends PDFDictionary {

function PDFWebBookmark($title, $url) {
$this->PDFDictionary();
$this->Assign('Title', $title);
$action = new PDFDictionary();
$action->Assign('S', Name('URI'));
$action->Assign('URI', $url);
$this->AssignObj('A', $action);
}

function SetParent(&$parent) {
$this->Assign('Parent', $parent);
if(!$parent->IsAssigned('First'))
{
$parent->AssignObj('First', $this);
}
if($parent->IsAssigned('Last'))
{
$last =& $parent->Object('Last');
$last->AssignObj('Next', $this);
$this->AssignObj('Prev', $last);
}
$parent->AssignObj('Last', $this);
}
}

class PDFPageLabel extends PDFDictionary {

function PDFPageLabel($style, $prefix = False, $encoding =
'WinAnsiEncoding') {
if($style)
{
$this->Assign('S', Name($style));
}
if($prefix)
{
$this->Assign('P', ToUnicodeStr($prefix, $encoding));
}
}
}

class PDFKernedString extends PDFArray {

function PDFKernedString($value = False, $offset = False) {
$this->PDFArray();
if($value)
{
$this->Add($value);
if($offset) $this->Add($offset);
}
}

function Prepend($obj) {
if(sizeof($this->values) > 0)
{
$this->values[0]->Prepend($obj);
}
else
{
$this->Add($obj);
}
}

function Append($obj, $offset = False) {
$last_index = sizeof($this->values) - 1;
if($last_index < 0 || get_class($this->values[$last_index]) ==
'pdfinteger')
{
$this->Add($obj);
}
else
{
if(get_class($obj) == 'pdfkernedstring')
{
$this->values[$last_index]->Append($obj->values[0]);
for($i = 1; $i < sizeof($obj->values); $i++)
{
$this->AddObj($obj->values[$i]);
}
}
else
{
$this->values[$last_index]->Append($obj);
}
}
if($offset)
{
$this->Add(- $offset);
}
}
}

class PDFFont extends PDFDictionary {
var $ref_name;
var $widths;
var $rExtents;
var $kerning_pairs;
var $properties;

function PDFFont() {
global $font_number;
$font_number++;
$ref_name = "F$font_number";
$this->PDFDictionary('Font');
$this->Assign('Name', Name($ref_name));
$this->ref_name = $ref_name;
}

function Transform($text, $size) {
$len = strlen($text);
if($this->kerning_pairs)
{
for($i = 0; $i < $len; $i++)
{
$char = ord($text{$i});
$width += $this->widths[$char];
if($kerning_offsets = $this->kerning_pairs[$char])
{
$next_index = $i + 1;
$next_char = ord($text{$next_index});
if($offset = $kerning_offsets[$next_char])
{
if(!$kerned_string) $kerned_string = new PDFKernedString();
$kerned_string->Append(substr($text, $last_kerned_char, $next_index -
$last_kerned_char), $offset);
$last_kerned_char = $next_index;
$width += $offset;
}
}
}
}
else
{
for($i = 0; $i < $len; $i++)
{
$char = ord($text{$i});
$width += $this->widths[$char];
}
}
$pt_width = $width * $size / 1000;
$r_extent = $this->rExtents[ord($text{$len - 1})] * $size / 1000;
if($kerned_string)
{
$kerned_string->Append(substr($text, $last_kerned_char));
return array($kerned_string, $pt_width, $r_extent);
}
else
{
return array(new PDFString($text), $pt_width, $r_extent);
}
}

function GetCharWidth($char, $size) {
return $this->widths[ord($char)] * $size / 1000;
}

function GetTextWidth($text, $size) {
$len = strlen($text);
if($this->kerning_pairs)
{
for($i = 0; $i < $len; $i++)
{
$char = ord($text{$i});
$width += $this->widths[$char];
if($kerning_offsets = $this->kerning_pairs[$char])
{
$next_index = $i + 1;
$next_char = ord($text{$next_index});
if($offset = $kerning_offsets[$next_char])
{
$width += $offset;
}
}
}
}
else
{
for($i = 0; $i < $len; $i++)
{
$char = ord($text{$i});
$width += $this->widths[$char];
}
}
$pt_width = $width * $size / 1000;
return $pt_width;
}
}

function ParseCPGFile($filePath) {
$char_to_unicode_map = array();
$lines = file($filePath);
foreach($lines as $line)
{
if(list($unicodeHex, $charCodeHex) = explode("\t", $line))
{
if(!strncmp($unicodeHex, '0x', 2) && !strncmp($charCodeHex, '0x', 2))
{
$unicode = hexdec(substr($unicodeHex, 2));
$charCode = hexdec(substr($charCodeHex, 2));
$char_to_unicode_map[$charCode] = $unicode;
}
}
}
return $char_to_unicode_map;
}

function ParseENCFile($filePath) {
$char_to_name_map = array();
$lines = file($filePath);
foreach($lines as $line)
{
if(list($name, $charCode) = preg_split('/\\s+/', $line, -1,
PREG_SPLIT_NO_EMPTY))
{
if($name{0} != '%' && is_numeric($charCode))
{
$char_to_name_map[(int) $charCode] = $name;
}
}
}
return $char_to_name_map;
}

$winAnsiEncodingNameMap = array(
32 => 'space', 33 => 'exclam', 34 => 'quotedbl', 35 => 'numbersign',
36 => 'dollar', 37 => 'percent', 38 => 'ampersand', 39 => 'quotesingle',
40 => 'parenleft', 41 => 'parenright', 42 => 'asterisk', 43 => 'plus',
44 => 'comma', 45 => 'hyphen', 46 => 'period', 47 => 'slash',
48 => 'zero', 49 => 'one', 50 => 'two', 51 => 'three',
52 => 'four', 53 => 'five', 54 => 'six', 55 => 'seven',
56 => 'eight', 57 => 'nine', 58 => 'colon', 59 => 'semicolon',
60 => 'less', 61 => 'equal', 62 => 'greater', 63 => 'question',
64 => 'at', 65 => 'A', 66 => 'B', 67 => 'C',
68 => 'D', 69 => 'E', 70 => 'F', 71 => 'G',
72 => 'H', 73 => 'I', 74 => 'J', 75 => 'K',
76 => 'L', 77 => 'M', 78 => 'N', 79 => 'O',
80 => 'P', 81 => 'Q', 82 => 'R', 83 => 'S',
84 => 'T', 85 => 'U', 86 => 'V', 87 => 'W',
88 => 'X', 89 => 'Y', 90 => 'Z', 91 => 'bracketleft',
92 => 'backslash', 93 => 'bracketright', 94 => 'asciicircum', 95 =>
'underscore',
96 => 'grave', 97 => 'a', 98 => 'b', 99 => 'c',
100 => 'd', 101 => 'e', 102 => 'f', 103 => 'g',
104 => 'h', 105 => 'i', 106 => 'j', 107 => 'k',
108 => 'l', 109 => 'm', 110 => 'n', 111 => 'o',
112 => 'p', 113 => 'q', 114 => 'r', 115 => 's', 116 => 't', 117 => 'u',
118 => 'v', 119 => 'w',
120 => 'x', 121 => 'y', 122 => 'z', 123 => 'braceleft',
124 => 'bar', 125 => 'braceright', 126 => 'asciitilde', 128 => 'Euro',
130 => 'quotesinglbase',131 => 'florin', 132 => 'quotedblbase', 133 =>
'ellipsis',
134 => 'dagger', 135 => 'daggerdbl', 136 => 'circumflex', 137 =>
'perthousand',
138 => 'Scaron', 139 => 'guilsinglleft', 140 => 'OE', 142 => 'Zcaron',
145 => 'quoteleft', 146 => 'quoteright', 147 => 'quotedblleft', 148 =>
'quotedblright',
150 => 'endash', 151 => 'emdash', 152 => 'tilde', 153 => 'trademark',
154 => 'scaron', 155 => 'guilsinglright',156 => 'oe', 158 => 'zcaron',
159 => 'Ydieresis', 161 => 'exclamdown', 162 => 'cent', 163 => 'sterling',
164 => 'currency1', 165 => 'yen', 166 => 'brokenbar', 167 => 'section',
168 => 'dieresis', 169 => 'copyright', 170 => 'ordfeminine', 171 =>
'guillemotleft',
172 => 'logicalnot', 174 => 'registered', 175 => 'macron', 176 => 'degree',
177 => 'plusminus', 178 => 'twosuperior', 179 => 'threesuperior', 180 =>
'acute',
181 => 'mu', 182 => 'paragraph', 183 => 'periodcentered',184 => 'cedilla',
185 => 'onesuperior', 186 => 'ordmasculine', 187 => 'guillemotright',188 =>
'onequarter',
189 => 'onehalf', 190 => 'threequarters', 191 => 'questiondown', 192 =>
'Agrave',
193 => 'Aacute', 194 => 'Acircumflex', 195 => 'Atilde', 196 => 'Adieresis',
197 => 'Aring', 198 => 'AE', 199 => 'Ccedilla', 200 => 'Egrave',
201 => 'Eacute', 202 => 'Ecircumflex', 203 => 'Edieresis', 204 => 'Igrave',
205 => 'Iacute', 206 => 'Icircumflex', 207 => 'Idieresis', 208 => 'Eth',
209 => 'Ntilde', 210 => 'Ograve', 211 => 'Oacute', 212 => 'Ocircumflex',
213 => 'Otilde', 214 => 'Odieresis', 215 => 'multiply', 216 => 'Oslash',
217 => 'Ugrave', 218 => 'Uacute', 219 => 'Ucircumflex', 220 => 'Udieresis',
221 => 'Yacute', 222 => 'Thorn', 223 => 'germandbls', 224 => 'agrave',
225 => 'aacute', 226 => 'acircumflex', 227 => 'atilde', 228 => 'adieresis',
229 => 'aring', 230 => 'ae', 231 => 'ccedilla', 232 => 'egrave',
233 => 'eacute', 234 => 'ecircumflex', 235 => 'edieresis', 236 => 'igrave',
237 => 'iacute', 238 => 'icircumflex', 239 => 'idieresis', 240 => 'eth',
241 => 'ntilde', 242 => 'ograve', 243 => 'oacute', 244 => 'ocircumflex',
245 => 'otilde', 246 => 'odieresis', 247 => 'divide', 248 => 'oslash',
249 => 'ugrave', 250 => 'uacute', 251 => 'ucircumflex', 252 => 'udieresis',
253 => 'yacute', 254 => 'thorn', 255 => 'ydieresis');

$winAnsiEncodingUnicodeMap = array(
0x20 => 0x20, 0x21 => 0x21, 0x22 => 0x22, 0x23 => 0x23,
0x24 => 0x24, 0x25 => 0x25, 0x26 => 0x26, 0x27 => 0x27,
0x28 => 0x28, 0x29 => 0x29, 0x2a => 0x2a, 0x2b => 0x2b,
0x2c => 0x2c, 0x2d => 0x2d, 0x2e => 0x2e, 0x2f => 0x2f,
0x30 => 0x30, 0x31 => 0x31, 0x32 => 0x32, 0x33 => 0x33,
0x34 => 0x34, 0x35 => 0x35, 0x36 => 0x36, 0x37 => 0x37,
0x38 => 0x38, 0x39 => 0x39, 0x3a => 0x3a, 0x3b => 0x3b,
0x3c => 0x3c, 0x3d => 0x3d, 0x3e => 0x3e, 0x3f => 0x3f,
0x40 => 0x40, 0x41 => 0x41, 0x42 => 0x42, 0x43 => 0x43,
0x44 => 0x44, 0x45 => 0x45, 0x46 => 0x46, 0x47 => 0x47,
0x48 => 0x48, 0x49 => 0x49, 0x4a => 0x4a, 0x4b => 0x4b,
0x4c => 0x4c, 0x4d => 0x4d, 0x4e => 0x4e, 0x4f => 0x4f,
0x50 => 0x50, 0x51 => 0x51, 0x52 => 0x52, 0x53 => 0x53,
0x54 => 0x54, 0x55 => 0x55, 0x56 => 0x56, 0x57 => 0x57,
0x58 => 0x58, 0x59 => 0x59, 0x5a => 0x5a, 0x5b => 0x5b,
0x5c => 0x5c, 0x5d => 0x5d, 0x5e => 0x5e, 0x5f => 0x5f,
0x60 => 0x60, 0x61 => 0x61, 0x62 => 0x62, 0x63 => 0x63,
0x64 => 0x64, 0x65 => 0x65, 0x66 => 0x66, 0x67 => 0x67,
0x68 => 0x68, 0x69 => 0x69, 0x6a => 0x6a, 0x6b => 0x6b,
0x6c => 0x6c, 0x6d => 0x6d, 0x6e => 0x6e, 0x6f => 0x6f,
0x70 => 0x70, 0x71 => 0x71, 0x72 => 0x72, 0x73 => 0x73,
0x74 => 0x74, 0x75 => 0x75, 0x76 => 0x76, 0x77 => 0x77,
0x78 => 0x78, 0x79 => 0x79, 0x7a => 0x7a, 0x7b => 0x7b,
0x7c => 0x7c, 0x7d => 0x7d, 0x7e => 0x7e, 0x80 => 0x20ac,
0x82 => 0x201a, 0x83 => 0x192, 0x84 => 0x201e, 0x85 => 0x2026,
0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x2c6, 0x89 => 0x2030,
0x8a => 0x160, 0x8b => 0x2039, 0x8c => 0x152, 0x8e => 0x17d,
0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, 0x94 => 0x201d,
0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x2dc, 0x99 => 0x2122,
0x9a => 0x161, 0x9b => 0x203a, 0x9c => 0x153, 0x9e => 0x17e,
0x9f => 0x178, 0xa1 => 0xa1, 0xa2 => 0xa2, 0xa3 => 0xa3,
0xa4 => 0xa4, 0xa5 => 0xa5, 0xa6 => 0xa6, 0xa7 => 0xa7,
0xa8 => 0xa8, 0xa9 => 0xa9, 0xaa => 0xaa, 0xab => 0xab,
0xac => 0xac, 0xae => 0xae, 0xaf => 0xaf, 0xb0 => 0xb0,
0xb1 => 0xb1, 0xb2 => 0xb2, 0xb3 => 0xb3, 0xb4 => 0xb4,
0xb5 => 0xb5, 0xb6 => 0xb6, 0xb7 => 0xb7, 0xb8 => 0xb8,
0xb9 => 0xb9, 0xba => 0xba, 0xbb => 0xbb, 0xbc => 0xbc,
0xbd => 0xbd, 0xbe => 0xbe, 0xbf => 0xbf, 0xc0 => 0xc0,
0xc1 => 0xc1, 0xc2 => 0xc2, 0xc3 => 0xc3, 0xc4 => 0xc4,
0xc5 => 0xc5, 0xc6 => 0xc6, 0xc7 => 0xc7, 0xc8 => 0xc8,
0xc9 => 0xc9, 0xca => 0xca, 0xcb => 0xcb, 0xcc => 0xcc,
0xcd => 0xcd, 0xce => 0xce, 0xcf => 0xcf, 0xd0 => 0xd0,
0xd1 => 0xd1, 0xd2 => 0xd2, 0xd3 => 0xd3, 0xd4 => 0xd4,
0xd5 => 0xd5, 0xd6 => 0xd6, 0xd7 => 0xd7, 0xd8 => 0xd8,
0xd9 => 0xd9, 0xda => 0xda, 0xdb => 0xdb, 0xdc => 0xdc,
0xdd => 0xdd, 0xde => 0xde, 0xdf => 0xdf, 0xe0 => 0xe0,
0xe1 => 0xe1, 0xe2 => 0xe2, 0xe3 => 0xe3, 0xe4 => 0xe4,
0xe5 => 0xe5, 0xe6 => 0xe6, 0xe7 => 0xe7, 0xe8 => 0xe8,
0xe9 => 0xe9, 0xea => 0xea, 0xeb => 0xeb, 0xec => 0xec,
0xed => 0xed, 0xee => 0xee, 0xef => 0xef, 0xf0 => 0xf0,
0xf1 => 0xf1, 0xf2 => 0xf2, 0xf3 => 0xf3, 0xf4 => 0xf4,
0xf5 => 0xf5, 0xf6 => 0xf6, 0xf7 => 0xf7, 0xf8 => 0xf8,
0xf9 => 0xf9, 0xfa => 0xfa, 0xfb => 0xfb, 0xfc => 0xfc,
0xfd => 0xfd, 0xfe => 0xfe, 0xff => 0xff );

$charSetNameMaps = array('WinAnsiEncoding' => $winAnsiEncodingNameMap);
$charSetUnicodeMaps = array('WinAnsiEncoding' =>
$winAnsiEncodingUnicodeMap);

function GetCharSetUnicodeMap($charSet) {
global $charMapDirectory, $charSetUnicodeMaps;
if(!($map = $charSetUnicodeMaps[$charSet]))
{
if(!$charMapDirectory)
{
die("GetCharSetUnicodeMap: global variable \$charMapDirectory not set");
}
$map = ParseCPGFile("$charMapDirectory/$charSet.cpg");
$charSetUnicodeMaps[$charSet] = $map;
}
return $map;
}

function GetCharSetNameMap($charSet) {
global $charMapDirectory, $charSetNameMaps;
if(!($map = $charSetNameMaps[$charSet]))
{
if(!$charMapDirectory)
{
die("GetCharSetNameMap: global variable \$charMapDirectory not set");
}
$map = ParseENCFile("$charMapDirectory/$charSet.enc");
$charSetNameMaps[$charSet] = $map;
}
return $map;
}

function GetInverseMap($map) {
$imap = array();
foreach($map as $apple => $orange)
{
if(!$imap[$orange]) !$imap[$orange] = array();
$imap[$orange][] = $apple;
}
return $imap;
}

class PDFEncodingCMap extends PDFFlateStream {
var $CIDSystemInfo;

function PDFEncodingCMap($charSet, $char_to_glyph_map) {
$this->PDFFlateStream('CMap');
$this->Assign('CMapName', $charSet);
$this->CIDSystemInfo = new PDFDictionary();
$this->CIDSystemInfo->Assign('Registry', $charSet);
$this->CIDSystemInfo->Assign('Ordering', $charSet);
$this->CIDSystemInfo->Assign('Supplement', 0);
$this->AssignObj('CIDSystemInfo', $this->CIDSystemInfo);
$this->Write(<<<CMAP_HEADER
/CIDInit /ProcSet findresource
begin
12 dict
begin
begincmap
/CIDSystemInfo <</Registry ($charSet) /Ordering ($charSet) /Supplement 0 >>
def
/CMapName /$charSet def 1
/CMapType 1 def
CMAP_HEADER
);
$this->Write("1 begincodespacerange\n<00> <FF>\nendcodespacerange\n");
$glyphRangeStart = false;
$nextGlyphId = -1;
$nextCharCode = -1;
$cidRangeEntries = array();
foreach($char_to_glyph_map as $charCode => $glyphId)
{
if($nextCharCode == $charCode && $glyphId == $nextGlyphId)
{
$rangeEnd = $charCode;
$nextGlyphId++;
$nextCharCode++;
}
else
{
if($glyphRangeStart)
{
$cidRangeEntries[] = sprintf('<%02x> <%02x> %d', $rangeStart, $rangeEnd,
$glyphRangeStart);
$glyphRangeStart = false;
$nextGlyphId = -1;
$nextCharCode = -1;
}
if($glyphId)
{
$rangeStart = $charCode;
$rangeEnd = $charCode;
$glyphRangeStart = $glyphId;
$nextGlyphId = $glyphId + 1;
$nextCharCode = $charCode + 1;
}
}
}
if($glyphRangeStart)
{
$cidRangeEntries[] = sprintf('<%02x> <%02x> %d', $rangeStart, $rangeEnd,
$glyphRangeStart);
}
for($j = 0, $j_bound = count($cidRangeEntries); $j < $j_bound; $j += 100)
{
$c = min(100, $j_bound - $j);
$this->Write("$c begincidrange\n");
$this->Write(implode("\n", array_slice($cidRangeEntries, $j, $c)));
$this->Write("\nendcidrange\n");
}
$this->Write(<<<CMAP_TRAILER
endcmap
CMapName
currentdict
/CMap
defineresource
pop
end
end
CMAP_TRAILER
);
}
}

class PDFToUnicodeCMap extends PDFFlateStream {

function PDFToUnicodeCMAP($charSet, $char_to_unicode_map) {
$this->PDFFlateStream();
$this->Write(<<<CMAP_HEADER
/CIDInit /ProcSet findresource
begin
12 dict
begin
begincmap
/CIDSystemInfo <</Registry ($charSet) /Ordering ($charSet) /Supplement 0 >>
def
/CMapName /$charSet def 1
/CMapType 1 def
CMAP_HEADER
);
$this->Write("1 begincodespacerange\n<00> <FF>\nendcodespacerange\n");
$unicodeRangeStart = false;
$nextUnicode = -1;
$nextCharCode = -1;
$cidRangeEntries = array();
foreach($char_to_unicode_map as $charCode => $unicode)
{
if($charCode == $nextCharCode && $unicode == $nextUnicode)
{
$rangeEnd = $charCode;
$nextUnicode++;
$nextCharCode++;
}
else
{
if($unicodeRangeStart)
{
$cidRangeEntries[] = sprintf('<%02x> <%02x> <%04x>', $rangeStart,
$rangeEnd, $unicodeRangeStart);
$unicodeRangeStart = false;
$nextUnicode = -1;
$nextCharCode = -1;
}
if($unicode)
{
$rangeStart = $charCode;
$rangeEnd = $charCode;
$unicodeRangeStart = $unicode;
$nextUnicode = $unicode + 1;
$nextCharCode = $charCode + 1;
}
}
}
if($unicodeRangeStart)
{
$cidRangeEntries[] = sprintf('<%02x> <%02x> <%04x>', $rangeStart,
$rangeEnd, $unicodeRangeStart);
}
for($j = 0, $j_bound = count($cidRangeEntries); $j < $j_bound; $j += 100)
{
$c = min(100, $j_bound - $j);
$this->Write("$c beginbfrange\n");
$this->Write(implode("\n", array_slice($cidRangeEntries, $j, $c)));
$this->Write("\nendbfrange\n");
}
$this->Write(<<<CMAP_TRAILER
endcmap
CMapName
currentdict
/CMap
defineresource
pop
end
end
CMAP_TRAILER
);
}
}

class PDFType1Font extends PDFFont {
var $encoding;
var $toUnicode;
var $widthArray;
var $descriptor;
var $stream;

function PDFType1Font($name, $charSet = 'WinAnsiEncoding', $builtIn =
false) {
global $Type1Directory;
if(!$Type1Directory)
{
die("PDFType1Font: global variable \$Type1Directory not set");
}
$this->PDFFont();
if($charSet != 'WinAnsiEncoding') $char_to_unicode_map =
GetCharSetUnicodeMap($charSet);
$char_to_name_map = GetCharSetNameMap($charSet);
$name_to_char_map = GetInverseMap($char_to_name_map);
$afm_file = fopen("$Type1Directory/$name.afm", "rt");
$properties = array();
$widths = array();
$rExtents = array();
$kerning_pairs = array();
$name_to_index_map = array();

while($line = fgets($afm_file, 256))
{
if($in_char_metrics)
{
if(strstr($line, 'EndCharMetrics'))
{
$in_char_metrics = False;
}
else
{
$metric_items = explode(';', $line);
$char_name = substr($metric_items[2], 3, -1);
if($char_name)
{
$bbox = explode(' ', substr($metric_items[3], 3, -1));
$width = (int) substr($metric_items[1], 4, -1);
$rExtent = $bbox[2] - $width;
if($a = $name_to_char_map[$char_name])
{
foreach($a as $charCode)
{
$widths[$charCode] = $width;
$rExtents[$charCode] = $rExtent;
$c = chr($charCode);
}
}
}
}
}
else if($in_kern_pairs)
{
if(strstr($line, 'EndKernPairs'))
{
$in_kern_pairs = False;
}
else
{
$kern_pair_items = explode(' ', $line);
$aLeft = $name_to_char_map[$kern_pair_items[1]];
$aRight = $name_to_char_map[$kern_pair_items[2]];
$offset = (int) $kern_pair_items[3];
if($aLeft && $aRight)
{
foreach($aLeft as $charCodeLeft)
{
if(!$kerning_pairs[$charCodeLeft]) $kerning_pairs[$charCodeLeft] =
array();
foreach($aRight as $charCodeRight)
{
$kerning_pairs[$charCodeLeft][$charCodeRight] = $offset;
}
}
}
}
}
else
{
$i = strpos($line, ' ');
$key = substr($line, 0, $i);
if($key == 'StartCharMetrics')
{
$in_char_metrics = True;
}
else if($key == 'StartKernPairs')
{
$in_kern_pairs = True;
}
else if($key)
{
$value = trim(substr($line, $i));
$properties[$key] = $value;
}
}
}
fclose($afm_file);
$this->Assign('Subtype', Name('Type1'));
if($charSet != 'WinAnsiEncoding')
{
$this->encoding = new PDFDictionary('Encoding');
$differences = new PDFArray();
$nextCharCode = -1;
foreach($char_to_name_map as $charCode => $charName)
{
if($nextCharCode != $charCode)
{
$differences->Add($charCode);
$nextCharCode = $charCode;
}
$differences->AddObj(Name($charName));
$nextCharCode++;
}
$this->encoding->AssignObj('Differences', $differences);
$this->AssignObj('Encoding', $this->encoding);
$this->toUnicode = new PDFToUnicodeCmap($charSet, $char_to_unicode_map);
$this->AssignObj('ToUnicode', $this->toUnicode);
}
else
{
$this->Assign('Encoding', Name('WinAnsiEncoding'));
}
if(!$builtIn)
{
$this->widthArray = new PDFArray();
$charCodes = array_keys($widths);
sort($charCodes);
$firstChar = $charCodes[0];
$lastChar = $charCodes[count($charCodes) - 1];
for($i = $firstChar; $i <= $lastChar; $i++)
{
$this->widthArray->Add((int) $widths[$i]);
}
$this->Assign('FirstChar', $firstChar);
$this->Assign('LastChar', $lastChar);
$this->AssignObj('Widths', $this->widthArray);

$this->descriptor = new PDFDictionary('FontDescriptor');
$this->descriptor->Assign('FontName', Name($properties['FontName']));
$bbox = explode(' ', $properties['FontBBox']);
$this->descriptor->Assign('FontBBox', new PDFRect($bbox[0], $bbox[1],
$bbox[2], $bbox[3]));
$this->descriptor->Assign('ItalicAngle', (float)
$properties['italicAngle']);
$this->descriptor->Assign('Ascent', (int) $properties['Ascender']);
$this->descriptor->Assign('Descent', (int) $properties['Descender']);
$this->descriptor->Assign('CapHeight', (int) $properties['CapHeight']);
$this->descriptor->Assign('XHeight', (int) $properties['XHeight']);
$this->descriptor->Assign('StemH', (int) $properties['StdHW']);
$this->descriptor->Assign('StemV', (int) $properties['StdVW']);
$flags = (($properties['IsFixedPitch'] == 'true') ? 0x0001 : 0x0000) |
($properties['CharacterSet'] == 'Special') ? 0x0004 : 0x0020;
if($properties['italicAngle'] > 0) $flags |= 0x0040;
$this->descriptor->Assign('Flags', $flags);
$this->stream = new PDFType1FileStream("$Type1Directory/$name.pfb");
$this->descriptor->AssignObj('FontFile', $this->stream);
$this->AssignObj('FontDescriptor', $this->descriptor);
}
$this->Assign('BaseFont', Name($properties['FontName']));
$this->widths = $widths;
$this->rExtents = $rExtents;
$this->kerning_pairs = $kerning_pairs;
}
}

class PDFBuiltInFont extends PDFType1Font {

function PDFBuiltInFont($name, $charSet = 'WinAnsiEncoding') {
$this->PDFType1Font($name, $charSet, true);
}
}

function UnpackTag($t) {
return chr($t >> 24) . chr(($t >> 16) & 0xFF) . chr(($t >> 8) & 0xFF) .
chr($t & 0xFF);
}

function CollapseUCS16String($s) {
$result = '';
for($i = 1, $i_bound = strlen($s); $i < $i_bound; $i += 2)
{
$result .= $s[$i];
}
return $result;
}

function FWord(&$d) {
if($d & 0x8000)
{
$d = - ($d ^ 0xFFFF);
}
}

function Fixed(&$d) {
$d = ($d / 65536);
}

define('SIZEOF_BYTE', 1);
define('SIZEOF_CHAR', 1);
define('SIZEOF_USHORT', 2);
define('SIZEOF_SHORT', 2);
define('SIZEOF_ULONG', 4);
define('SIZEOF_LONG', 4);
define('SIZEOF_FIXED', 4);

class PDFTrueTypeFont extends PDFFont {
var $descriptor;
var $CIDFont;
var $encoding;
var $toUnicode;
var $stream;

function PDFTrueTypeFont($filename, $charSet = 'WinAnsiEncoding') {
global $TTFDirectory;
if(!$TTFDirectory)
{
die("PDFType1Font: global variable \$TTFDirectory not set");
}
$this->PDFFont();
$char_to_unicode_map = GetCharSetUnicodeMap($charSet);
$file = fopen("$TTFDirectory/$filename", "rb");
$offsetTableBin = fread($file, SIZEOF_FIXED + SIZEOF_USHORT * 4);
$offsetTable =
unpack("Nversion/nnumTables/nsearchRange/nentrySelector/nrangeShift",
$offsetTableBin);
$tableDir = Array();
for($i = 0, $i_bound = $offsetTable['numTables']; $i < $i_bound; $i++)
{
$tableDirEntryBin = fread($file, SIZEOF_ULONG * 4);
$tableDirEntry = unpack("Ntag/NcheckSum/Noffset/Nlength",
$tableDirEntryBin);
$tag = UnpackTag($tableDirEntry['tag']);
$tableDir[$tag] = $tableDirEntry;
}
$headEntry = $tableDir['head'];
fseek($file, $headEntry['offset']);
$headBin = fread($file, $headEntry['length']);
$head =
unpack("NtableVersion/NfontRevision/NcheckSumAdjustment/NmagicNumber/nflags/
nunitsPerEm/a8created/a8modified/nxMin/nyMin/nxMax/nyMax/nmacStyle/nlowestRe
cPPEM/nfontDirectionHint/nindexToLocFormat/nglyphDataFormat", $headBin);
FWord($head['xMin']);
FWord($head['yMin']);
FWord($head['xMax']);
FWord($head['yMax']);
$pdf_unit_ratio = 1000 / $head['unitsPerEm'];

$postEntry = $tableDir['post'];
fseek($file, $postEntry['offset']);
$postBin = fread($file, $postEntry['length']);
$post =
unpack("NformatType/NitalicAngle/nunderlinePosition/nunderlineThickness/NisF
ixedPitch/NminMemType42/NmaxMemType42/NminMemType1/NmaxMemType1", $postBin);
Fixed($post['formatType']);
Fixed($post['italicAngle']);
FWord($post['underlinePosition']);

$hheaEntry = $tableDir['hhea'];
fseek($file, $hheaEntry['offset']);
$hheaBin = fread($file, $hheaEntry['length']);
$hhea =
unpack("NtableVersion/nascender/ndescender/nlineGap/nadvanceWidthMax/nminLef
tSideBearing/nminRightSideBearing/nxMaxExtent/ncaretSlopeRise/ncareSlopRun/n
5reserved/nmetricDataFormat/nnumMetrics", $hheaBin);
FWord($hhea['ascender']);
FWord($hhea['descender']);
FWord($hhea['lineGap']);
FWord($hhea['minLeftSideBearing']);
FWord($hhea['minRightSideBearing']);
FWord($hhea['xMaxExtent']);

$os2Entry = $tableDir['OS/2'];
fseek($file, $os2Entry['offset']);
$os2Bin = fread($file, $os2Entry['length']);
$os2 =
unpack("nversion/navgCharWidth/nweightClass/nwidthClass/nfsType/nsubscriptXS
ize/nsubscriptYSize/nsubscriptXOffset/nsubscriptYOffset/nsuperscriptXSize/ns
uperscriptYSize/nsuperscriptXOffset/nsuperscriptYOffset/nstrikeoutSize/nstri
keoutPosition/nfamilyClass/a10panose/N4unicodeRange/a4vendId/nselection/nfir
stCharIndex/nlastCharIndex/ntypoAscender/ntypoDescender/ntypeLineGap/nwinAsc
ent/nwinDescent/NcodePageRange1/NcodePageRange2", $os2Bin);
FWord($os2['typoDescender']);

if($pcltEntry = $tableDir['PCLT'])
{
fseek($file, $pcltEntry['offset']);
$pcltBin = fread($file, $pcltEntry['length']);
$pclt =
unpack("Nversion/NfontNumber/npitch/nxHeight/nstyle/ntypeFamily/ncapHeight/n
symbolSet/a16typeFace/a8characterComplement/a6filename/CstrokeWeight/CwidthT
ype/CserifStyle/Creserved", $pcltBin);
}

$nameEntry = $tableDir['name'];
fseek($file, $nameEntry['offset']);
$nameBin = fread($file, $nameEntry['length']);
$name = unpack("nformat/nnumRecords/noffset", $nameBin);
$nameRecordSize = SIZEOF_USHORT * 6;
for($i = 0, $i_bound = $name['numRecords'], $offset = SIZEOF_USHORT * 3; $i
< $i_bound; $i++, $offset += $nameRecordSize)
{
$nameRecordBin = substr($nameBin, $offset, $nameRecordSize);
$nameRecord =
unpack("nplatformId/nencodingId/nlangId/nnameId/nlength/noffset",
$nameRecordBin);
if($nameRecord['nameId'] == 6)
{
fseek($file, $nameEntry['offset'] + $name['offset'] +
$nameRecord['offset']);
$postscriptName = CollapseUCS16String(fread($file,
$nameRecord['length']));
break;
}
}

$cmapEntry = $tableDir['cmap'];
fseek($file, $cmapEntry['offset']);
$cmapBin = fread($file, $cmapEntry['length']);
$cmap = unpack("ntableVersion/nnumEncodings", $cmapBin);
$encodingEntrySize = (SIZEOF_USHORT * 2) + SIZEOF_ULONG;
for($i = 0, $i_bound = $cmap['numEncodings'], $offset = SIZEOF_USHORT * 2;
$i < $i_bound; $i++, $offset += $encodingEntrySize)
{
$encodingEntryBin = substr($cmapBin, $offset, $encodingEntrySize);
$encodingEntry = unpack("nplatformId/nencodingId/Noffset",
$encodingEntryBin);
if($encodingEntry['platformId'] == 3 && $encodingEntry['encodingId'] == 1)
{
$unicodeMapBin = substr($cmapBin, $encodingEntry['offset'], SIZEOF_USHORT
* 7);
$unicodeMap =
unpack("nformat/nlength/nversion/nsegCountX2/nsearchRange/nentrySelector/nra
ngeShift", $unicodeMapBin);
$segCount = $unicodeMap['segCountX2'] / 2;
$offset = $encodingEntry['offset'] + SIZEOF_USHORT * 7;
$endCodesBin = substr($cmapBin, $offset, SIZEOF_USHORT * $segCount);
$endCodes = unpack("n$segCount", $endCodesBin);
$offset += SIZEOF_USHORT * $segCount + SIZEOF_USHORT;
$startCodesBin = substr($cmapBin, $offset, SIZEOF_USHORT * $segCount);
$startCodes = unpack("n$segCount", $startCodesBin);
$offset += SIZEOF_USHORT * $segCount;
$idDeltasBin = substr($cmapBin, $offset, SIZEOF_USHORT * $segCount);
$idDeltas = unpack("n$segCount", $idDeltasBin);
array_walk($idDeltas, 'FWord');
$offset += SIZEOF_USHORT * $segCount;
$rangeOffsetsBin = substr($cmapBin, $offset, SIZEOF_USHORT * $segCount);
$rangeOffsets = unpack("n$segCount", $rangeOffsetsBin);
$offset += SIZEOF_USHORT * $segCount;
$glyphIdCount = ($unicodeMap['length'] - SIZEOF_USHORT * (($segCount * 4
+ 8))) / SIZEOF_USHORT;
$glyphIdsBin = substr($cmapBin, $offset, SIZEOF_USHORT * $glyphIdCount);
$glyphIds = unpack("n$glyphIdCount", $glyphIdsBin);
$char_to_glyph_map = array();
$glyph_to_char_map = array();
$maxGlyphId = 0;
foreach($char_to_unicode_map as $charCode => $unicode)
{
for($j = 1; $j < $segCount; $j++)
{
if($unicode <= $endCodes[$j])
{
if($unicode >= $startCodes[$j])
{
if($rangeOffsets[$j] == 0)
{
$glyphId = $unicode + $idDeltas[$j] - 1;
}
else
{
$offset = ($rangeOffsets[$j] / 2) - ($segCount - $j) + ($unicode -
$startCodes[$j]);
$glyphId = $glyphIds[$offset];
}
$char_to_glyph_map[$charCode] = $glyphId;
// need inverse map for mapping kerning info
if(!$glyph_to_char_map[$glyphId]) $glyph_to_char_map[$glyphId] =
array();
$glyph_to_char_map[$glyphId][] = $charCode;
$maxGlyphId = max($maxGlyphId, $glyphId);
}
else
{
break;
}
}
}
}
break;
}
}

$widths = array();
$gWidths = array();
$hmtxEntry = $tableDir['hmtx'];
fseek($file, $hmtxEntry['offset']);
$hmtxBin = fread($file, $hmtxEntry['length']);
foreach($char_to_glyph_map as $charCode => $glyphId)
{
$offset = $glyphId * SIZEOF_USHORT * 2;
$metricBin = substr($hmtxBin, $offset, SIZEOF_USHORT * 2);
$metric = unpack("nadvanceWidth/nlsb", $metricBin);
$width = round($metric['advanceWidth'] * $pdf_unit_ratio);
$widths[$charCode] = $width;
$gWidths[$glyphId] = $width;
}

if($kernEntry = $tableDir['kern'])
{
$kerning_pairs = array();
fseek($file, $kernEntry['offset']);
$kernBin = fread($file, $kernEntry['length']);
$kern = unpack("ntableVersion/nnumSubTables", $cmapBin);
for($i = 0, $i_bound = $kern['numSubTables'], $offset = SIZEOF_USHORT * 2;
$i < $i_bound; $i++)
{
$kerningEntryBin = substr($kernBin, $offset, SIZEOF_USHORT * 3);
$kerningEntry = unpack("nversion/nlength/ncoverage", $kerningEntryBin);
$format = $kerningEntry['coverage'] >> 8;
$horizontal = ($kerningEntry['coverage'] & 0x0001) ? true : false;
if($horizontal && $format == 0)
{
$kerningSubTableBin = substr($kernBin, $offset + SIZEOF_USHORT * 3,
SIZEOF_USHORT * 4);
$kerningSubTable =
unpack("nnumPairs/nsearchRange/nentrySelector/nrangeShift",
$kerningSubTableBin);
$kerningPairSize = SIZEOF_USHORT * 3;
for($j = 0, $j_bound = $kerningSubTable['numPairs'], $offset = $offset +
SIZEOF_USHORT * 4; $j < $j_bound; $j++, $offset += $kerningPairSize)
{
$kerningPairBin = substr($kernBin, $offset, $kerningPairSize);
$kerningPair = unpack("nleft/nright/nvalue", $kerningPairBin);
FWord($kerningPair['value']);
$glyphIdLeft = $kerningPair['left'];
$glyphIdRight = $kerningPair['right'];
$aLeft = $glyph_to_char_map[$glyphIdLeft];
$aRight = $glyph_to_char_map[$glyphIdRight];
if($aLeft && $aRight)
{
$kerningOffset = $kerningPair['value'] * $pdf_unit_ratio;
foreach($aLeft as $charCodeLeft)
{
if(!$kerning_pairs[$charCodeLeft]) $kerning_pairs[$charCodeLeft] =
array();
foreach($aRight as $charCodeRight)
{
$kerning_pairs[$charCodeLeft][$charCodeRight] = $kerningOffset;
}
}
if($maxGlyphId < $glyphIdLeft && $maxGlyphId < $glyphIdRight) {
break; }
}
}
break;
}
$offset += $kerningEntry['length'];
}
}

$w = new PDFArray();
$rangeWidths = false;
$nextCharCode = -1;
foreach($gWidths as $glyphId => $width)
{
if($glyphId == $nextGlyphId)
{
$rangeWidths[] = $width;
$nextGlyphId++;
}
else
{
if($rangeWidths)
{
$w->Add($rangeStart);
$w->Add($rangeWidths);
}
$rangeStart = $glyphId;
$rangeWidths = array($width);
$nextGlyphId = $glyphId + 1;
}
}
$w->Add($rangeStart);
$w->Add($rangeWidths);

$this->descriptor = new PDFDictionary('FontDescriptor');
$this->stream = new PDFTrueTypeFileStream("$TTFDirectory/$filename");
$this->encoding = new PDFEncodingCMap($charSet, $char_to_glyph_map);
$this->toUnicode = new PDFToUnicodeCMap($charSet, $char_to_unicode_map);
$this->Assign('Subtype', Name('Type0'));
$this->AssignObj('Encoding', $this->encoding);
$this->AssignObj('ToUnicode', $this->toUnicode);
$this->CIDFont = new PDFDictionary('Font');
$this->CIDFont->Assign('Subtype', Name('CIDFontType2'));
$this->CIDFont->AssignObj('FontDescriptor', $this->descriptor);
$this->CIDFont->AssignObj('CIDSystemInfo', $this->encoding->CIDSystemInfo);
$this->CIDFont->AssignObj('W', $w);
$this->descriptor->AssignObj('FontFile2', $this->stream);
$descendantFonts = new PDFArray();
$descendantFonts->AddObj($this->CIDFont);
$this->AssignObj('DescendantFonts', $descendantFonts);

$this->Assign('BaseFont', Name($postscriptName));
$this->CIDFont->Assign('BaseFont', Name($postscriptName));
$this->descriptor->Assign('FontName', Name($postscriptName));
$left = round($head['xMin'] * $pdf_unit_ratio);
$bottom = round($head['yMin'] * $pdf_unit_ratio);
$right = round($head['xMax'] * $pdf_unit_ratio);
$top = round($head['yMax'] * $pdf_unit_ratio);
$this->descriptor->Assign('FontBBox', new PDFRect($left, $bottom, $right,
$top));
$this->descriptor->Assign('ItalicAngle', $post['italicAngle']);
$this->descriptor->Assign('Ascent', round($os2['typoAscender'] *
$pdf_unit_ratio));
$this->descriptor->Assign('Descent', round($os2['typoDescender'] *
$pdf_unit_ratio));
$this->descriptor->Assign('AvgWidth', round($os2['avgCharWidth'] *
$pdf_unit_ratio));
$this->descriptor->Assign('Leading', round($os2['typeLineGap'] *
$pdf_unit_ratio));
if($pclt)
{
$this->descriptor->Assign('CapHeight', round($pclt['capHeight'] *
$pdf_unit_ratio));
$this->descriptor->Assign('XHeight', round($pclt['xHeight'] *
$pdf_unit_ratio));
}
else
{
$this->descriptor->Assign('CapHeight', round($os2['typoAscender']*
$pdf_unit_ratio));
}
$this->descriptor->Assign('StemV', 45);
$flags = $post['isFixedPitch'] | ($os2['codePageRange1'] & 0x8000000) ?
0x0004 : 0x0020;
if($os2['panose1'] == 3) $flags |= 0x0008;
if($os2['panose2'] != 11 && $os2['panose2'] != 12 && $os2['panose2'] != 13)
$flags |= 0x0002;
if($post['italicAngle']) $flags |= 0x0040;
$this->descriptor->Assign('Flags', $flags);
$this->widths = $widths;
$this->kerning_pairs = $kerning_pairs;
}
}

class PDFPageLabels extends PDFDictionary {
var $nums;

function PDFPageLabels() {
$this->PDFDictionary('PageLabels');
$this->nums = new PDFArray();
$this->AssignObj('Nums', $this->nums);
}

function Add($start_index, &$label) {
$this->nums->Add($start_index);
$this->nums->AddObj($label);
}
}

class PDFOutlines extends PDFDictionary {

function PDFOutlines() {
$this->PDFDictionary('Outlines');
$this->Assign('Count', 0);
}

function IncrementCount() {
$count =& $this->Object('Count');
$count->Add(1);
}
}

class Rect {
var $left;
var $right;
var $top;
var $bottom;
var $width;
var $height;

function Rect($left, $bottom, $right, $top) {
$this->left = $left;
$this->bottom = $bottom;
$this->right = $right;
$this->top = $top;
$this->width = $right - $left;
$this->height = $top - $bottom;
}

function Intersect($rect) {
return (min($this->top, $rect->top) > max($this->bottom, $rect->bottom))
&& (min($this->right, $rect->right) > max($this->left, $rect->left));
}
}

class PDFSpacing {
var $left;
var $right;
var $top;
var $bottom;

function PDFSpacing($left, $bottom, $right, $top) {
$this->left = $left;
$this->bottom = $bottom;
$this->right = $right;
$this->top = $top;
}
}

class PDFPadding extends PDFSpacing {
function PDFPadding($left, $bottom, $right, $top) {
$this->PDFSpacing($left, $bottom, $right, $top);
}
}

class PDFBorder {
var $left;
var $right;
var $top;
var $bottom;
var $color;

function PDFBorder($left, $bottom, $right, $top, $color) {
$this->left = $left;
$this->bottom = $bottom;
$this->right = $right;
$this->top = $top;
$this->color = $color;
}

function Simple() {
return ($this->left === $this->right) && ($this->left === $this->top) &&
($this->left === $this->bottom);
}
}

class PDFRegion {
var $rects;
var $bounding_rects;
var $padding;
var $spacing;
var $minWidth;
var $alloc_index;
var $alloc_top;
var $prev_left;
var $prev_right;

function PDFRegion($rects, $spacing = 0, $padding = 0, $minWidth = 144) {
$this->rects = $rects;
$this->padding = is_object($padding) ? $padding : new PDFPadding($padding,
$padding, $padding, $padding);
$this->spacing = is_object($spacing) ? $spacing : new PDFSpacing($spacing,
$spacing, $spacing, $spacing);
$this->minWidth = $minWidth;
$this->alloc_index = 0;
$this->alloc_top = -1;
}

function AllocLineRect($lineHeight) {
while($this->alloc_index < count($this->rects))
{
$a = $this->rects[$this->alloc_index];
$top = ($this->alloc_top != -1) ? $this->alloc_top : $a->top -
$this->padding->top;
$bottom = $top - $lineHeight;
$this->alloc_top = $bottom;
if($bottom >= $a->bottom + $this->padding->bottom)
{
if($top <= $a->top - $this->padding->top)
{
return new Rect($a->left + $this->padding->left, $bottom, $a->right -
$this->padding->right, $top);
}
else
{
return new Rect($this->prev_left + $this->padding->left, $bottom,
$this->prev_right - $this->padding->right, $top);
}
}
else
{
$b = $this->rects[$this->alloc_index + 1];
if($b->top == $a->bottom)
{
$this->alloc_index++;
if($bottom >= $b->bottom + $this->padding->bottom)
{
$this->prev_left = max($b->left, $a->left);
$this->prev_right = min($b->right, $a->right);
return new Rect($this->prev_left + $this->padding->left, $bottom,
$this->prev_right - $this->padding->right, $top);
}
}
else
{
// move to the next, disconnected rect
$this->alloc_index++;
$this->alloc_top = -1;
}
}
}
return null;
}

function RemoveRect($rect) {
$newRects = array();
foreach($this->rects as $a)
{
if($a->Intersect($rect))
{
$b1 = $b2 = $b3 = $b4 = false;
$top = $a->top;
$ctop = $rect->top + $this->spacing->top;
if($top > $ctop)
{
$b1 = new Rect($a->left, $ctop, $a->right, $top);
$top = $ctop;
}
$bottom = $a->bottom;
$cbottom = $rect->bottom - $this->spacing->bottom;
if($bottom < $cbottom)
{
$b4 = new Rect($a->left, $bottom, $a->right, $cbottom);
$bottom = $cbottom;
}
$left = $a->left;
$cleft = $rect->left - $this->spacing->left;
if($left < $cleft)
{
$b2 = new Rect($left, $bottom, $cleft, $top);
if($b2->width < $this->minWidth) $b2 = false;
}
$right = $a->right;
$cright = $rect->right + $this->spacing->right;
if($right > $cright)
{
$b3 = new Rect($cright, $bottom, $right, $top);
if($b3->width < $this->minWidth) $b3 = false;
}
if($b1) $newRects[] = $b1;
if($b2 || $b3)
{
if(!$b3)
{
$newRects[] = $b2;
}
else if(!$b2)
{
$newRects[] = $b3;
}
else
{
$newRects[] = ($b2->width >= $b3->width) ? $b2 : $b3;
}
}
if($b4) $newRects[] = $b4;
}
else
{
$newRects[] = $a;
}
}
$this->rects = $newRects;
}

function RemoveRects($rects) {
foreach($rects as $rect)
{
$this->RemoveRect($rect);
}
}

function GetTop() {
return ($this->rects) ? $this->rects[0]->top : 0;
}
}

class PDFRectangularRegion extends PDFRegion {

function PDFRectangularRegion($left, $bottom, $right, $top, $spacing = 0,
$padding = 0, $minWidth = 144) {
$this->PDFRegion(array(new Rect($left, $bottom, $right, $top)), $spacing,
$padding, $minWidth);
}
}

class PDFMultiColumnRegion extends PDFRegion {

function PDFMultiColumnRegion($left, $bottom, $right, $top, $col, $gutter =
18, $spacing = 0, $padding = 0, $minWidth = -1) {
if(is_scalar($padding)) $padding = new PDFPadding($padding, $padding,
$padding, $padding);
$paddedWidth = $padding->left + $padding->right;
$colWidth = (int) round((($right - $left) - $paddedWidth - ($gutter *
($col - 1))) / $col);
$rects = array();
for($i = 0, $offset = 0; $i < $col; $i++)
{
$rects[] = new Rect($left + $offset, $bottom, $left + $offset + $colWidth
+ $paddedWidth, $top);
$offset += $colWidth + $gutter;
}
$this->PDFRegion($rects, $spacing, $padding, ($minWidth >= 0) ? $minWidth :
(int) $colWidth / 2);
}
}

function Bound($n, $min = 0, $max = 1) {
if($n > $max) return 1;
else if($n < $min) return 0;
return $n;
}

class PDFRGBChromaKey extends PDFArray {

function PDFRGBChromaKey($r, $g, $b, $delta = 0.01) {
$this->PDFArray();
$this->Add(round(Bound($r - $delta) * 255));
$this->Add(round(Bound($r + $delta) * 255));
$this->Add(round(Bound($g - $delta) * 255));
$this->Add(round(Bound($g + $delta) * 255));
$this->Add(round(Bound($g - $delta) * 255));
$this->Add(round(Bound($g + $delta) * 255));
}
}

class PDFGrayChromaKey extends PDFArray {

function PDFGrayChromaKey($grayLevel, $delta = 0.01) {
$this->PDFArray();
$this->Add(round(Bound($grayLevel - $delta) * 255));
$this->Add(round(Bound($grayLevel + $delta) * 255));
}

function ToRGB() {
$this->values[2] = $this->values[0];
$this->values[3] = $this->values[1];
$this->values[4] = $this->values[0];
$this->values[5] = $this->values[1];
}
}

define('UPPER_LEFT', 0);
define('UPPER_RIGHT', 1);
define('LOWER_LEFT', 2);
define('LOWER_RIGHT', 3);
define('CENTER', 4);
define('CENTER_LEFT', 5);
define('CENTER_RIGHT', 6);

$image_number = 0;

class PDFJPEGImageStream extends PDFFileStream {
var $ref_name;
var $width;
var $height;
var $is_color;

function PDFJPEGImageStream($filepath, $chromaKey = false) {
global $image_number, $imageDirectory;
if(basename($filepath) == $filepath)
{
$filepath = "$imageDirectory/$filepath";
}
$this->PDFFileStream($filepath);
$image_number++;
$this->ref_name = "Im$image_number";
$this->PDFDictionary('XObject');
$this->Assign('Filter', Name('DCTDecode'));
$this->size = filesize($filepath);
$img_info = GetImageSize($filepath);
$this->width = $img_info[0];
$this->height = $img_info[1];
$this->is_color = ($img_info['channels'] == 3);
$this->Assign('Length', $this->size);
$this->Assign('Subtype', Name('Image'));
$this->Assign('Width', $this->width);
$this->Assign('Height', $this->height);
$this->Assign('ColorSpace', Name($this->is_color ? 'DeviceRGB' :
'DeviceGray'));
$this->Assign('BitsPerComponent', 8);
if($chromaKey)
{
// fix image/chroma-key mismatch
if($this->is_color && get_class($chromaKey) == 'pdfgraychromakey')
{
$chromaKey->ToRGB();
}
else if(!$this->is_color && get_class($chromaKey) == 'pdfrgbchromakey')
{
// tricky situation: a color chroma-key was defined but the image turns
out to be monochromatic
// set choma-key to white, since that's the likeliest background color
$chromaKey = new PDFGrayChromaKey(1);
}
$this->Assign('Mask', $chromaKey);
}
}

function Width() {
return $this->width;
}

function Height() {
return $this->height;
}

function IsColor() {
return $this->is_color;
}

function GetRect($x, $y, $origin = UPPER_LEFT, $dpi = 96) {
$pt_width = round($this->width * 72 / $dpi, 4);
$pt_height = round($this->height * 72 / $dpi, 4);
switch($origin) {
case UPPER_LEFT:
return new Rect($x, $y - $pt_height, $x + $pt_width, $y);
break;
case UPPER_RIGHT:
return new Rect($x - $pt_width , $y - $pt_height, $x, $y);
break;
case LOWER_LEFT:
return new Rect($x, $y, $x + $pt_width, $y + $pt_height);
break;
case LOWER_RIGHT:
return new Rect($x - $pt_width, $y, $x, $y + $pt_height);
break;
case CENTER:
return new Rect($x - $pt_width / 2, $y - $pt_height / 2, $x + $pt_width /
2, $y + $pt_height / 2);
break;
case CENTER_LEFT:
return new Rect($x, $y - $pt_height / 2, $x + $pt_width, $y + $pt_height
/ 2);
break;
case CENTER_RIGHT:
return new Rect($x - $pt_width, $y - $pt_height / 2, $x, $y + $pt_height
/ 2);
break;
}
}
}

class TextStyle {
var $font;
var $size;
var $rise;
var $color;
}

function JoinWords(&$line, $obj, $addSpace) {
$last_index = sizeof($line) - 1;
if($last_index >= 0)
{
if($addSpace)
{
for($i = $last_index; $i >= 0; $i--)
{
if(get_class($line[$i]) != 'textstyle')
{
$obj->Prepend(' ');
break;
}
}
}
$last_obj = &$line[$last_index];
if(get_class($last_obj) == 'textstyle')
{
$line[] = $obj;
}
else
{
if(get_class($last_obj) == 'pdfstring' && get_class($obj) ==
'pdfkernedstring')
{
$obj->Prepend($last_obj);
$line[$last_index] = $obj;
}
else
{
$last_obj->Append($obj);
}
}
}
else
{
$line[] = $obj;
}
}

define('DELIMITER_NOT_ADDED', 0x01);
define('WORD_NOT_ADDED', 0x02);
define('WORD_IS_TOO_LONG', 0x04);

define('JUSTIFY_LEFT', 0);
define('JUSTIFY_RIGHT', 1);
define('JUSTIFY_CENTER', 2);
define('JUSTIFY_FULL', 3);

class PDFTextArea {
var $region;
var $spacing;
var $current_justify;
var $current_text_style;
var $current_line_spacing;
var $current_leading;
var $current_line;
var $current_line_width;
var $current_line_right_extent;
var $current_line_word_count;
var $current_line_char_count;
var $current_line_space_count;
var $current_line_box;
var $current_font;
var $space_width;
var $last_text_style;
var $stream;

function PDFTextArea(&$stream, $region) {
$this->stream = &$stream;
$this->current_line = array();
$this->current_line_width = 0;
$this->current_line_word_count = 0;
$this->current_justify = JUSTIFY_FULL;
$this->current_text_style = new TextStyle();
$this->region = $region;
}

function LineFeed() {
if($this->current_line_box)
{
$this->WriteCurrentLine(True);
unset($this->current_line_box);
}
else
{
$this->region->AllocLineRect($this->current_leading);
}
}

function CarrierReturn() {
if($this->current_line_box)
{
$this->WriteCurrentLine(True);
}
}

function SetJustify($justify) {
$this->current_justify = $justify;
}

function SetLineSpacing($spacing) {
$this->current_line_spacing = $spacing;
$this->SetLeading($this->current_text_style->size + $spacing);
}

function SetLeading($leading) {
$this->current_leading = $leading;
}

function SetColor($color) {
if(is_scalar($color)) $color = new PDFGray($color);
$this->current_text_style->color = $color;
$this->current_line[] = $this->current_text_style;
}

function SetFont($font, $size, $text_rise = 0) {
$this->current_font = $font;
$this->current_text_style->font = $font->ref_name;
$this->current_text_style->size = $size;
$this->current_text_style->rise = $text_rise;
$this->space_width = $font->GetCharWidth(' ', $size);
$this->current_line[] = $this->current_text_style;
$this->SetLeading($size + $this->current_line_spacing);
}

function Indent($width) {
$this->current_line_box->width -= $width;
switch($this->current_justify)
{
case JUSTIFY_FULL:
case JUSTIFY_LEFT:
$this->current_line_box->left += $width;
break;
case JUSTIFY_CENTER:
$this->current_line_box->right -= (int) floor($width / 2);
$this->current_line_box->left += (int) ceil($width / 2);
case JUSTIFY_RIGHT:
$this->current_line_box->right -= $width;
}
}

function WriteCurrentLine($line_break = False) {
$extra_width = $this->current_line_box->width - $this->current_line_width -
$this->current_line_right_extent;
$y = $this->current_line_box->bottom;
switch($this->current_justify)
{
case JUSTIFY_FULL:
$x = $this->current_line_box->left;
if(!$line_break)
{
// fill in extra space by expanding both character spacing and word
spacing
$space_count = $this->current_line_space_count;
$char_gap_count = $this->current_line_char_count - 1;
if($char_gap_count > 0)
{
$cs_portion = $extra_width / (($space_count * 0.25) + 1); // becomes
smaller with more words
if($space_count > 0)
{
$ws_portion = $extra_width - $cs_portion;
$this->ApplyWordSpacing(Round($ws_portion / $space_count, 4));
}
$this->ApplyCharSpacing(Round($cs_portion / $char_gap_count, 4));
}
}
else
{
$this->ApplyWordSpacing(0);
$this->ApplyCharSpacing(0);
}
break;
case JUSTIFY_LEFT:
$x = $this->current_line_box->left;
$this->ApplyWordSpacing(0);
break;
case JUSTIFY_RIGHT:
$x = $this->current_line_box->left + $extra_width;
$this->ApplyWordSpacing(0);
break;
case JUSTIFY_CENTER:
$x = $this->current_line_box->left + ($extra_width / 2);
$this->ApplyWordSpacing(0);
break;
}
$this->stream->Write("BT\n$x $y Td\n");
foreach($this->current_line as $obj)
{
if(get_class($obj) == 'textstyle')
{
$this->ApplyTextStyle($obj);
}
else
{
$this->WriteText($obj);
}
}
$this->stream->Write("ET\n");
$this->current_line = array();
$this->current_line_width = 0;
$this->current_line_word_count = 0;
$this->current_line_char_count = 0;
$this->current_line_space_count = 0;
}

function WriteText($obj) {
if(!$obj->IsEmpty())
{
$this->stream->Write($obj->ToString());
$this->stream->Write(get_class($obj) == 'pdfkernedstring' ? " TJ\n" : "
Tj\n");
}
}

function ApplyTextStyle($style) {
if($style != $this->last_text_style)
{
if(!$this->last_text_style || $this->last_text_style->font != $style->font
|| $this->last_text_style->size !=
$style->size)
{
if($style->font)
{
$this->stream->Write("/$style->font $style->size Tf\n");
}
}
if((!$this->last_text_style && $style->rise > 0) ||
$this->last_text_style->rise != $style->rise)
{
$this->stream->Write("$style->rise Ts\n");
}
if($style->color && (!$this->last_text_style ||
$this->last_text_style->color != $style->color))
{
$this->stream->Write($style->color->ToStringNonStroking());
}
$this->last_text_style = $style;
}
}

function ApplyWordSpacing($spacing) {
if($this->word_spacing != $spacing)
{
$this->stream->Write("$spacing Tw\n");
$this->word_spacing = $spacing;
}
}

function ApplyCharSpacing($spacing) {
$this->stream->Write("$spacing Tc\n");
}

function AddWord($separator, $word) {
$flags = DELIMITER_NOT_ADDED | WORD_NOT_ADDED;
$char_count = strlen($word);
list($word_obj, $word_width, $right_extent) =
$this->current_font->Transform($word, $this->current_text_style->size);
if($separator == ' ')
{
if($this->current_line_word_count > 0)
{
$separator_width = $this->space_width;
$separator_char_count = 1;
}
}
else if($separator == "\x09" && $this->current_line_word_count == 0)
{
if($this->current_line_box || $this->current_line_box =
$this->region->AllocLineRect($this->current_leading))
{
$this->Indent($this->space_width * 4);
$flags &= ~DELIMITER_NOT_ADDED;
}
}
else if($separator)
{
if($separator == "\x09") $separator = "\xA0\xA0\xA0\xA0"; // change tab
into 4 non-breaking spaces
list($separator_obj, $separator_width, $separator_right_extent) =
$this->current_font->Transform($separator, $this->current_text_style->size);
$separator_char_count = strlen($separator);
}
while($this->current_line_box || ($this->current_line_box =
$this->region->AllocLineRect($this->current_leading)))
{
if($this->current_line_width + $separator_width + $word_width <=
$this->current_line_box->width)
{
if($separator_obj)
{
JoinWords($this->current_line, $separator_obj, false);
JoinWords($this->current_line, $word_obj, false);
}
else
{
JoinWords($this->current_line, $word_obj, ($separator_char_count > 0));
}
$this->current_line_width += $separator_width + $word_width;
$this->current_line_word_count++;
$this->current_line_char_count += $separator_char_count + $char_count;
$this->current_line_right_extent = $right_extent;
if($separator == ' ') $this->current_line_space_count +=
$separator_char_count;
$flags &= ~(DELIMITER_NOT_ADDED | WORD_NOT_ADDED);
break;
}
else if($separator_obj && $this->current_line_width + $separator_width <=
$this->current_line_box->width) // add delimiter to current line if possible
{
JoinWords($this->current_line, $separator_obj, false);
$this->current_line_width += $separator_width;
$this->current_line_char_count += $separator_char_count;
$this->current_line_right_extent = $separator_right_extent;
$this->WriteCurrentLine();
unset($this->current_line_box);
$separator_obj = null;
$separator_width = 0;
$separator_char_count = 0;
$flags &= ~DELIMITER_NOT_ADDED;
}
else if($this->current_line_width > 0)
{
$this->WriteCurrentLine();
unset($this->current_line_box);
if($separator == ' ')
{
$separator_char_count = 0;
$separator_width = 0;
}
}
else
{
$flags |= WORD_IS_TOO_LONG;
break;
}
}
return $flags;
}

function AddText($text, $end_of_paragraph = true) {
if(!$this->current_font)
{
die("Use PDFTextArea::SetText() to set current font before adding text");
}
if(is_array($text))
{
foreach($text as $index => $line)
{
if($leftover = $this->AddText(chop($line)))
{
$leftover_lines = array_slice($text, $index + 1);
array_unshift($leftover_lines, $leftover);
return $leftover_lines;
}
}
return array();
}
else
{
$written_count = 0;
$matches = array();

if(preg_match_all('/([\\x20\\x09\\x85\\x97-]?)([^\\x20\\x09\\x85\\x97-]*)/',
$text, $matches, PREG_PATTERN_ORDER))
{
$separators = $matches[1];
$words = $matches[2];
foreach($words as $index => $word)
{
$separator = $separators[$index];
if($separator || $word)
{
$flags = $this->AddWord($separator, $word);
if(!($flags & DELIMITER_NOT_ADDED))
{
$written_count += strlen($separator);
}
if(!($flags & WORD_NOT_ADDED))
{
$written_count += strlen($word);
}
else
{
return substr($text, $written_count);
}
}
}
}
if($end_of_paragraph)
{
$this->LineFeed();
}
return '';
}
}

function GetCurrentLineTop() {
return ($this->current_line_box) ? $this->current_line_box :
$this->region->GetTop();
}
}

define('BUTT_CAP', 0);
define('ROUND_CAP', 1);
define('PROJECTING_SQUARE_CAP', 2);

define('MITER_JOIN', 0);
define('ROUND_JOIN', 1);
define('BEVEL_JOIN', 2);

class PDFPage extends PDFDictionary {
var $width;
var $height;
var $stream;
var $occupied_rects;

function PDFPage($width, $height) {
$this->PDFDictionary('Page');
$this->width = $width;
$this->height = $height;
$this->Assign('MediaBox', new PDFRect(0, 0, $width, $height));
$this->stream = new PDFFlateStream();
$this->AssignObj('Contents', $this->stream);
$this->occupied_rects = array();
}
function AddOccupiedRects($rects, $spacing) {
foreach($rects as $rect)
{
$this->occupied_rects[] = new Rect($rect->left - $spacing->left,
$rect->bottom - $spacing->bottom, $rect->right + $spacing->right, $rect->top
+ $spacing->top);
}
}

function MoveTo($x, $y) {
$this->stream->Write("$x $y m\n");
}

function LineTo($x, $y) {
$this->stream->Write("$x $y l\n");
}

function ClosePath() {
$this->stream->Write("h\n");
}

function SetLineColor($color) {
if(is_scalar($color)) $color = new PDFGray($color);
$this->stream->Write($color->ToStringStroking());
}

function SetFillColor($color) {
if(is_scalar($color)) $color = new PDFGray($color);
$this->stream->Write($color->ToStringNonStroking());
}

function SetLineWidth($width) {
$this->stream->Write("$width w\n");
}

function SetLineCap($cap) {
$this->stream->Write("$cap J\n");
}

function SetLineJoin($join) {
$this->stream->Write("$join j\n");
}

function DrawPath() {
$this->stream->Write("S\n");
}

function FillPath() {
$this->stream->Write("f\n");
}

function ClipPath() {
$this->stream->Write("W n\n");
}

function SaveGraphicState() {
$this->stream->Write("q\n");
}

function RestoreGraphicState() {
$this->stream->Write("Q\n");
}

function SetPath($region) {
$moveTo = true;
$endIndex = count($region->rects) - 1;
for($i = 0; $i <= $endIndex; $i++)
{
$rect = $region->rects[$i];
$next_rect = $region->rects[$i + 1];
if($moveTo) // start at top, left corner
{
$this->MoveTo($rect->left, $rect->top);
$startIndex = $i;
$moveTo = false;
}
if($i == $endIndex || $next_rect->top != $rect->bottom)
{
$this->LineTo($rect->left, $rect->bottom);
$this->LineTo($rect->right, $rect->bottom);
for($j = $i; $j >= $startIndex; $j--)
{
$rect = $region->rects[$j];
$prev_rect = $region->rects[$j - 1];
if($j == $startIndex)
{
$this->LineTo($rect->right, $rect->top);
$this->ClosePath();
}
else if($prev_rect->right != $rect->right)
{
$this->LineTo($rect->right, $rect->top);
$this->LineTo($prev_rect->right, $rect->top);
}
}
$moveTo = true;
}
else if($next_rect->left != $rect->left)
{
$this->LineTo($rect->left, $rect->bottom);
$this->LineTo($next_rect->left, $rect->bottom);
}
}
}

function FillRegion($region, $color) {
$this->SaveGraphicState();
$this->SetFillColor($color);
$this->SetPath($region);
$this->FillPath();
$this->RestoreGraphicState();
}

function DrawBorder($region, $border) {
if(is_scalar($border)) $border = new PDFBorder($border, $border, $border,
$border, BLACK);
$this->SaveGraphicState();
$this->SetLineColor($border->color);
if($border->Simple()) // if all four sides have the same border width
{
$this->SetLineWidth($border->left);
$this->SetPath($region);
$this->DrawPath();
}
else
{
$endIndex = count($region->rects) - 1;
$borderWidth = false;
if($border->left !== false)
{
$borderWidth = $border->left;
$this->SetLineWidth($borderWidth);
for($i = 0; $i <= $endIndex; $i++)
{
$rect = $region->rects[$i];
$next_rect = $region->rects[$i + 1];
if($i == 0)
{
$this->MoveTo($rect->left, $rect->top);
}
if($i == $endIndex)
{
$this->LineTo($rect->left, $rect->bottom);
}
else if($rect->bottom != $next_rect->top || $rect->left !=
$next_rect->left)
{
$this->LineTo($rect->left, $rect->bottom);
$this->MoveTo($next_rect->left, $next_rect->top);
}
}
$this->DrawPath();
}
if($border->right !== false)
{
if($border->right !== $borderWidth)
{
$borderWidth = $border->right;
$this->SetLineWidth($borderWidth);
}
for($i = 0; $i <= $endIndex; $i++)
{
$rect = $region->rects[$i];
$next_rect = $region->rects[$i + 1];
if($i == 0)
{
$this->MoveTo($rect->right, $rect->top);
}
if($i == $endIndex)
{
$this->LineTo($rect->right, $rect->bottom);
}
else if($rect->bottom != $next_rect->top || $rect->right !=
$next_rect->right)
{
$this->LineTo($rect->right, $rect->bottom);
$this->MoveTo($next_rect->right, $next_rect->top);
}
}
$this->DrawPath();
}
if($border->top !== false)
{
if($border->top !== $borderWidth)
{
$borderWidth = $border->top;
$this->SetLineWidth($borderWidth);
}
for($i = $endIndex; $i >= 0; $i--)
{
$rect = $region->rects[$i];
$prev_rect = $region->rects[$i - 1];
if($i == 0 || $rect->top != $prev_rect->bottom)
{
$this->MoveTo($rect->left, $rect->top);
$this->LineTo($rect->right, $rect->top);
}
else
{
if($rect->left < $prev_rect->left)
{
$this->MoveTo($rect->left, $rect->top);
$this->LineTo($prev_rect->left, $rect->top);
}
if($rect->right > $prev_rect->right)
{
$this->MoveTo($rect->right, $rect->top);
$this->LineTo($prev_rect->right, $rect->top);
}
}
}
$this->DrawPath();
}
if($border->bottom !== false)
{
if($border->bottom !== $borderWidth)
{
$borderWidth = $border->bottom;
$this->SetLineWidth($borderWidth);
}
for($i = 0; $i <= $endIndex; $i++)
{
$rect = $region->rects[$i];
$next_rect = $region->rects[$i + 1];
if($i == $endIndex || $rect->bottom != $next_rect->top)
{
$this->MoveTo($rect->left, $rect->bottom);
$this->LineTo($rect->right, $rect->bottom);
}
else
{
if($rect->left < $next_rect->left)
{
$this->MoveTo($rect->left, $rect->bottom);
$this->LineTo($next_rect->left, $rect->bottom);
}
if($rect->right > $next_rect->right)
{
$this->MoveTo($rect->right, $rect->bottom);
$this->LineTo($next_rect->right, $rect->bottom);
}
}
}
$this->DrawPath();
}
}
$this->RestoreGraphicState();
}

function InsertTextArea($region, $border = false, $backgroundColor = false,
$overlay = false) {
if(!$overlay)
{
$region->RemoveRects($this->occupied_rects);
$this->AddOccupiedRects($region->rects, $region->spacing);
}
if($backgroundColor !== false)
{
$this->FillRegion($region, $backgroundColor);
}
if($border !== false)
{
$this->DrawBorder($region, $border);
}
return new PDFTextArea($this->stream, $region);
}

function InsertImage($imageSrc, $rect, $spacing = 0, $border = false,
$overlay = false) {
if(!$imageSrc->Registered())
{
die("Use PDF::AddImageStream() to add image to resource table first");
}
$region = new PDFRegion(array($rect), $spacing, 0, 0);
if(!$overlay)
{
$region->RemoveRects($this->occupied_rects);
$this->AddOccupiedRects($region->rects, $region->spacing);
}
if(is_scalar($spacing))
{
$spacing = new PDFSpacing($spacing, $spacing, $spacing, $spacing);
}
$this->SaveGraphicState();
$this->SetPath($region);
$this->ClipPath();
$this->stream->Write("$rect->width 0 0 $rect->height $rect->left
$rect->bottom cm\n/$imageSrc->ref_name Do\n");
$this->RestoreGraphicState();
if($border !== false)
{
$this->DrawBorder($region, $border);
}
}

}

class PDFPages extends PDFDictionary {
var $default_width;
var $default_height;
var $kids;

function PDFPages() {
$this->PDFDictionary('Pages');
$this->kids = new PDFArray();
$this->AssignObj('Kids', $this->kids);
}

function Add(&$page, $index) {
if($index >= 0)
{
$this->kids->InsertObj($page, $index);
}
else
{
$index = $this->kids->AddObj($page);
}
$page->AssignObj('Parent', $this);
if(!$this->default_width || !$this->default_height)
{
$this->default_width = $page->width;
$this->default_height = $page->height;
$this->Assign('MediaBox', new PDFRect(0, 0, $this->default_width,
$this->default_height));
$page->Unassign('MediaBox');
}
else if($page->width == $this->default_width && $page->height ==
$this->default_height)
{
$page->Unassign('MediaBox');
}
$this->Assign('Count', $this->kids->Count());
return $index;
}
}

class PDFDocument {
var $file;
var $objects;
var $next_id;
var $current_offset;
var $pages;
var $page_labels;
var $outlines;
var $fonts;
var $trailer;
var $info;
var $xobjects;
var $prevBookmarks;

function PDFDocument() {
$this->objects = array();
$this->next_id = 1;
$this->current_offset = 0;
$this->fonts = new PDFDictionary();
$this->xobjects = new PDFDictionary();
$resources = new PDFDictionary();
$resources->Assign('ProcSet', array(Name('PDF'), Name('Text'),
Name('ImageB')));
$resources->AssignObj('Font', $this->fonts);
$resources->AssignObj('XObject', $this->xobjects);
$this->pages = new PDFPages();
$this->pages->AssignObj('Resources', $resources);
$this->page_labels = new PDFPageLabels();
$this->outlines = new PDFOutlines();
$this->info = new PDFDictionary();
$this->info->Assign('Creator', 'pdf_class.php');
$this->info->Assign('Producer', 'Chung Leong (ch***********@hotmail.com)');
$this->info->Assign('CreationDate', new PDFDate());
$catalog = new PDFDictionary('Catalog');
$catalog->AssignObj('Pages', $this->pages);
$catalog->AssignObj('PageLabels', $this->page_labels);
$catalog->AssignObj('Outlines', $this->outlines);
$this->trailer = new PDFDictionary();
$this->trailer->AssignObj('Root', $catalog);
$this->trailer->AssignObj('Info', $this->info);
$this->RegisterObject($this->outlines);
$this->RegisterObject($this->page_labels);
$this->RegisterObject($this->pages);
$this->RegisterObject($catalog);
$this->RegisterObject($this->info);
$this->prevBookmarks = array();
}

function SetDocInfo($name, $value) {
$this->info->Assign($name, $value);
}

function RegisterObject(&$obj) {
$id = $this->next_id++;
$this->objects[] = &$obj;
$obj->SetId($id);
$child_objs = get_object_vars($obj);
foreach($child_objs as $child_name => $child_obj)
{
if(is_subclass_of($child_obj, 'PDFObj') && !$child_obj->Registered())
{
$this->RegisterObject($obj->$child_name);
}
}
return $id;
}

function Write($s) {
if($this->file) fputs($this->file, $s);
else echo $s;
$this->current_offset += strlen($s);
}

function WriteObject(&$obj) {
if(!$obj->Registered())
{
$this->RegisterObject($obj);
}
$obj->SetOffset($this->current_offset);
$obj->Output($this);
}

function WritePage(&$page) {
$this->WriteObject($page);
$this->WriteObject($page->stream);
}

function Open($filename = false) {
if($filename)
{
$this->file = fopen($filename, "wb");
}
$this->Write("%PDF-1.3\n");
$this->Write("%++\n");
}

function AddFont(&$font) {
$this->fonts->AssignObj($font->ref_name, $font);
$this->RegisterObject($font);
}

function AddImageStream(&$stream) {
$this->xobjects->AssignObj($stream->ref_name, $stream);
$this->RegisterObject($stream);
}

function AddPage(&$page, $index = -1) {
$index = $this->pages->Add($page, $index);
$this->WritePage($page);
return $index;
}

function AddBookmark(&$bookmark, $depth = 0) {
if($depth == 0)
{
$bookmark->SetParent($this->outlines);
}
else
{
if($parent =& $this->prevBookmarks[$depth - 1])
{
$bookmark->SetParent($parent);
}
else
{
$pDepth = $depth - 1;
die("PDFPage::AddBookmark: No bookmark exists at parent level
($pDepth)");
}
}
$this->prevBookmarks[$depth] =& $bookmark;
$this->outlines->IncrementCount();
return $this->RegisterObject($bookmark);
}

function AddPageLabels($start_index, &$label) {
return $this->page_labels->Add($start_index, $label);
}

function Close() {
$object_count = sizeof($this->objects);
for($i = 0; $i < $object_count; $i++)
{
$obj =& $this->objects[$i];
if(!$obj->Written())
{
$this->WriteObject($obj);
}
}
$xref_count = $object_count + 1;
$xref_offset = $this->current_offset;
$this->Write("xref\n");
$this->Write("0 $xref_count\n");
$this->Write("0000000000 65535 f\r\n");
for($i = 0; $i < $object_count; $i++)
{
$this->Write(sprintf("%010d 00000 n\r\n", $this->objects[$i]->offset));
}
$this->Write("trailer\n");
$this->trailer->Assign('Size', $xref_count);
$this->Write($this->trailer->ToString());
$this->Write("\nstartxref\n$xref_offset\n");
$this->Write("%%EOF\n");
if($this->file)
{
fclose($this->file);
}
}
}

?>
Jul 17 '05 #3

This discussion thread is closed

Replies have been disabled for this discussion.