File: /home/xedaptot/cms.naniguide.com/includes/pdftotext/pdfToText.php
<?php
class PdfToText
{
/**
* Parse PDF file
*
* @param string $filename
* @return string
*/
public static function parseFile($filename)
{
$content = file_get_contents($filename);
return self::extractText($content);
}
/**
* Parse PDF content
*
* @param string $content
* @return string
*/
public static function parseContent($content)
{
return self::extractText($content);
}
/**
* Convert a PDF into text.
*
* @param string $filename The filename to extract the data from.
* @return string The extracted text from the PDF
*/
protected static function extractText($data)
{
/**
* Split apart the PDF document into sections. We will address each
* section separately.
*/
$a_obj = self::getDataArray($data, "obj", "endobj");
$j = 0;
$a_chunks = array();
/**
* Attempt to extract each part of the PDF document into a "filter"
* element and a "data" element. This can then be used to decode the
* data.
*/
foreach($a_obj as $obj){
$a_filter = self::getDataArray($obj, "<<", ">>");
if(is_array($a_filter) && isset($a_filter[0])){
$a_chunks[$j]['filter'] = $a_filter[0];
$a_data = self::getDataArray($obj, "stream", "endstream");
if(is_array($a_data) && isset($a_data[0]))
$a_chunks[$j]['data'] = trim(substr($a_data[0], strlen("stream"), strlen($a_data[0]) - strlen("stream") - strlen("endstream")));
$j++;
}
}
$result_data = null;
// decode the chunks
foreach($a_chunks as $chunk){
// Look at each chunk decide ifwe can decode it by looking at the contents of the filter
if(isset($chunk['data'])){
// look at the filter to find out which encoding has been used
if(strpos($chunk['filter'], "FlateDecode") !== false){
// Use gzuncompress but suppress error messages.
$data =@ gzuncompress($chunk['data']);
}else
$data = $chunk['data'];
if(trim($data) != ""){
// ifwe got data then attempt to extract it.
$result_data .= " " . self::extractTextElements($data);
}
}
}
/**
* Make sure we don"t have large blocks of white space before and after
* our string. Also extract alphanumerical information to reduce
* redundant data.
*/
if(trim($result_data) == "")
return null;
else{
// Optimize hyphened words
$result_data = preg_replace("/\s*-[\r\n]+\s*/", "", $result_data);
$result_data = preg_replace("/\s+/", " ", $result_data);
return $result_data;
}
}
protected static function extractTextElements($content)
{
if(strpos($content, "/CIDInit") === 0) return "";
$text = "";
$lines = explode("\n", $content);
foreach ($lines as $line){
$line = trim($line);
$matches = array();
// Parse each lines to extract command and operator values
if(preg_match("/^(?<command>.*[\)\] ])(?<operator>[a-z]+[\*]?)$/i", $line, $matches)){
$command = trim($matches['command']);
// Convert octal encoding
$found_octal_values = array();
preg_match_all("/\\\\([0-9]{3})/", $command, $found_octal_values);
foreach($found_octal_values[0] as $value){
$octal = substr($value, 1);
if(intval($octal) < 40){
// Skips non printable chars
$command = str_replace($value, "", $command);
}else
$command = str_replace($value, chr(octdec($octal)), $command);
}
// Removes encoded new lines, tabs, ...
$command = preg_replace("/\\\\[\r\n]/", "", $command);
$command = preg_replace("/\\\\[rnftb ]/", " ", $command);
// Force UTF-8 charset
$encoding = mb_detect_encoding($command, array("ASCII", "UTF-8", "Windows-1252", "ISO-8859-1"));
if(strtoupper($encoding) != "UTF-8"){
if($decoded = @iconv("CP1252", "UTF-8//TRANSLIT//IGNORE", $command))
$command = $decoded;
}
// Removes leading spaces
$operator = trim($matches['operator']);
}else{
$command = $line;
$operator = "";
}
// Handle main operators
switch ($operator){
// Set character spacing.
case "Tc":
break;
// Move text current point.
case "Td":
$values = explode(" ", $command);
$y = array_pop($values);
$x = array_pop($values);
if($x > 0)
$text .= " ";
if($y < 0)
$text .= " ";
break;
// Move text current point and set leading.
case "TD":
$values = explode(" ", $command);
$y = array_pop($values);
if($y < 0)
$text .= "\n";
break;
// Set font name and size.
case "Tf":
$text.= " ";
break;
// Display text, allowing individual character positioning
case "TJ":
$start = mb_strpos($command, "[", null, "UTF-8") + 1;
$end = mb_strrpos($command, "]", null, "UTF-8");
$text.= self::parseTextCommand(mb_substr($command, $start, $end - $start, "UTF-8"));
break;
// Display text.
case "Tj":
$start = mb_strpos($command, "(", null, "UTF-8") + 1;
$end = mb_strrpos($command, ")", null, "UTF-8");
$text.= mb_substr($command, $start, $end - $start, "UTF-8"); // Removes round brackets
break;
// Set leading.
case "TL":
// Set text matrix.
case "Tm":
break;
// Set text rendering mode.
case "Tr":
break;
// Set super/subscripting text rise.
case "Ts":
break;
// Set text spacing.
case "Tw":
break;
// Set horizontal scaling.
case "Tz":
break;
// Move to start of next line.
case "T*":
$text.= "\n";
break;
// Internal use
case "g":
case "gs":
case "re":
case "f":
// Begin text
case "BT":
// End text
case "ET":
break;
case "":
break;
default:
}
}
$text = str_replace(array("\\(", "\\)"), array("(", ")"), $text);
return $text;
}
/**
* Strip out the text from a small chunk of data.
*
* @param string $text
* @param int $font_size Currently not used
*
* @return string
*/
protected static function parseTextCommand($text, $font_size = 0){
$result = "";
$cur_start_pos = 0;
while(($cur_start_text = mb_strpos($text, "(", $cur_start_pos, "UTF-8")) !== false){
// New text element found
if($cur_start_text - $cur_start_pos > 8)
$spacing = " ";
else{
$spacing_size = mb_substr($text, $cur_start_pos, $cur_start_text - $cur_start_pos, "UTF-8");
$spacing = ($spacing_size < -50) ? " " : "";
}
$cur_start_text++;
$start_search_end = $cur_start_text;
while(($cur_start_pos = mb_strpos($text, ")", $start_search_end, "UTF-8")) !== false){
if(mb_substr($text, $cur_start_pos - 1, 1, "UTF-8") != "\\") break;
$start_search_end = $cur_start_pos + 1;
}
// something wrong happened
if($cur_start_pos === false) break;
// Add to result
$result .= $spacing . mb_substr($text, $cur_start_text, $cur_start_pos - $cur_start_text, "UTF-8");
$cur_start_pos++;
}
return $result;
}
/**
* Convert a section of data into an array, separated by the start and end words.
*
* @param string $data The data.
* @param string $start_word The start of each section of data.
* @param string $end_word The end of each section of data.
* @return array The array of data.
*/
protected static function getDataArray($data, $start_word, $end_word)
{
$start = 0;
$end = 0;
$a_results = array();
while($start !== false && $end !== false){
$start = strpos($data, $start_word, $end);
$end = strpos($data, $end_word, $start);
if($end !== false && $start !== false){
// data is between start and end
$a_results[] = substr($data, $start, $end - $start + strlen($end_word));
}
}
return $a_results;
}
}