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);
}
}
}
?>