recipe_pdf35.module in Recipe 6
recipe_pdf35.module - Enables exporting of 3x5" cards in pdf format. This is incredibly rudimentary at this point. 1 and only 1 card and if you go over, the text is lost.
File
plugins/recipe_pdf35.moduleView source
<?php
/**
* @file
* recipe_pdf35.module - Enables exporting of 3x5" cards in pdf format.
* This is incredibly rudimentary at this point. 1 and only 1 card and if you go over, the text is lost.
*/
/**
* Implementation of hook_recipeio($type).
*/
function recipe_pdf35_recipeio($type) {
$supported = array(
'export_single' => array(
'format_name' => t('PDF-3x5'),
'callback' => 'recipe_pdf35_export_single',
'format_help' => t('Export 3"x5" cards in pdf format.'),
// No special permissions for pdf export.
'access arguments' => 'access content',
),
);
if (isset($supported[$type])) {
return array(
'pdf35' => $supported[$type],
);
}
else {
return FALSE;
}
}
function recipe_pdf35_export_single($nid = NULL) {
if ($nid === NULL) {
drupal_set_message(t('Recipe not found.'));
drupal_not_found();
}
$node = node_load(array(
'nid' => $nid,
'type' => 'recipe',
));
// you should not be able to export unpublished recipes
if ($node->status == 0) {
drupal_access_denied();
return;
}
$recipe_data = prepare_receipe_data($node);
$pdf_pages = get_pdf_pages($recipe_data);
// This is a non-indented section for ease of making byte-accurate pdf strings.
// The newlines and spacing is pretty critical for this to work.
$pdf_index = array();
$objects = array(
'first item in the objects array is thrown out.',
);
// Resource Object, required by page objects.
$obj = <<<EOS
[/PDF /Text]
EOS;
array_push($objects, $obj);
$pdf_index['resource'] = count($objects) - 1;
// Font 1 Object, required by page objects.
$obj = <<<EOS
<< /Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Arial
/Encoding /WinAnsiEncoding
>>
EOS;
array_push($objects, $obj);
$pdf_index['font1'] = count($objects) - 1;
// Font 2 Object, required by page objects.
$obj = <<<EOS
<< /Type /Font
/Subtype /Type1
/Name /F2
/BaseFont /Courier
/Encoding /WinAnsiEncoding
>>
EOS;
array_push($objects, $obj);
$pdf_index['font2'] = count($objects) - 1;
$current_pos = count($objects) + 1;
$page_refs = array();
for ($i = 0; $i < count($pdf_pages); $i++) {
if (count($pdf_pages) > 1) {
$page_item_count = get_page_item_count($pdf_pages[$i]) + 1;
}
else {
$page_item_count = get_page_item_count($pdf_pages[$i]);
}
$page_refs[] = $current_pos + $page_item_count . ' 0 R';
$current_pos = $current_pos + $page_item_count + 1;
}
$kid_refs = implode(" ", $page_refs);
$kid_count = count($page_refs);
// Pages Object, required by root.
$obj = <<<EOS
<< /Type /Pages
/Kids [{<span class="php-variable">$kid_refs</span>}]
/Count {<span class="php-variable">$kid_count</span>}
>>
EOS;
array_push($objects, $obj);
$pdf_index['pages'] = count($objects) - 1;
// Render the objects for each page, then the page item.
for ($i = 0; $i < count($pdf_pages); $i++) {
foreach ($pdf_pages[$i]['header'] as $obj) {
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
foreach ($pdf_pages[$i]['column_0'] as $obj) {
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
foreach ($pdf_pages[$i]['column_1'] as $obj) {
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
foreach ($pdf_pages[$i]['column_2'] as $obj) {
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
foreach ($pdf_pages[$i]['footer'] as $obj) {
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
// Page Numbers
if (count($pdf_pages) > 1) {
$page_nbr = t('Page ' . ($i + 1));
$obj = "BT /F1 6 Tf 12 TL 325 17 Td ({$page_nbr})' ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
array_push($objects, $obj);
$pdf_pages[$i]['items'][] = "" . count($objects) - 1 . ' 0 R';
}
// Page Object
$content_refs = implode(' ', $pdf_pages[$i]['items']);
$obj = "<< /Type /Page\n/Parent {$pdf_index['pages']} 0 R\n/MediaBox [0 0 360 216]\n/Contents [{$content_refs}]\n/Resources << /ProcSet 4 0 R\n/Font << /F1 {$pdf_index['font1']} 0 R /F2 {$pdf_index['font2']} 0 R >>\n>>\n>>";
array_push($objects, $obj);
}
// Root Object, required by trailer.
$obj = <<<EOS
<< /Type /Catalog
/Pages {<span class="php-variable">$pdf_index</span>[<span class="php-string">'pages'</span>]} 0 R
>>
EOS;
array_push($objects, $obj);
$pdf_index['root'] = count($objects) - 1;
/* Loop the objects array for the actual pdf with object refs.
*
*/
$xref = array();
$pdf = "%PDF-1.4\n";
$pdf .= '%' . pack("c*", 128, 200, 225, 255) . "\n";
foreach ($objects as $obj_num => $o) {
// We skip array index 0.
if ($obj_num != 0) {
// Save the xref byte offset.
$xref[$obj_num] = strlen($pdf);
$pdf .= "{$obj_num} 0 obj\n";
$pdf .= "{$o}\n";
$pdf .= "endobj\n";
}
}
$xref_start_pos = strlen($pdf);
$xref_entry_count = count($xref) + 1;
$pdf .= "xref\n";
$pdf .= "0 {$xref_entry_count}\n";
$pdf .= "0000000000 65535 f \n";
foreach ($xref as $obj_num => $offset) {
$pdf .= sprintf("%010u 00000 n \n", $offset);
}
// Start of trailer section.
$pdf .= "trailer\n";
$pdf .= "<< /Size {$xref_entry_count}\n";
// Hard coded warning, /Root is the 1 0 object in this module.
$pdf .= "/Root {$pdf_index['root']} 0 R\n";
$pdf .= ">>\n";
$pdf .= "startxref\n";
$pdf .= "{$xref_start_pos}\n";
$pdf .= '%%EOF';
$file_name = strtolower($node->title) . '.pdf';
$file_name = str_replace(' ', '_', $file_name);
drupal_set_header('Content-type: application/pdf');
drupal_set_header($header = "Content-Disposition: attachment; filename={$file_name}");
return $pdf;
}
function get_page_item_count($page) {
// Count up the columns + header and footer.
return count($page['column_0']) + count($page['column_1']) + count($page['column_2']) + count($page['header']) + count($page['footer']);
}
function prepare_receipe_data($node) {
// Preprocess the ingredients.
$ingredients = array();
foreach ($node->ingredients as $key => $i) {
$fullingredient = strlen($i['note']) > 0 ? $i['name'] . ' (' . $i['note'] . ')' : $i['name'];
$quantity = recipe_ingredient_quantity_from_decimal($i['quantity'], TRUE);
$quantity = trim(str_replace('⁄', '/', $quantity));
$ingredients[] = $quantity . ' ' . $i['abbreviation'] . ' ' . $fullingredient;
}
// Preprocess the instructions.
$instructions_str = strip_html_and_encode_entities($node->instructions);
$data = array(
'recipe_title' => $node->title,
'ingredient_heading' => t('Ingredients'),
'instruction_heading' => t('Instructions'),
'ingredients' => $ingredients,
'instructions' => $instructions_str,
);
return $data;
}
function get_pdf_pages($recipe_data) {
$pages = array();
/*
* Handle the ingredients.
*/
// Put them in the correct column.
$col1 = array();
for ($i = 0; $i < count($recipe_data['ingredients']); $i++) {
$tmp_list = wrap_and_pdf_escape($recipe_data['ingredients'][$i], 35, "\n ");
$col1 = array_merge($col1, $tmp_list);
}
// setup first page.
$page = get_new_pdf_page($recipe_data);
$line = 1;
for ($i = 0; $i < count($col1); $i++) {
// You've filled the page, get a new one.
if ($line > 16) {
// End the strings in the columns.
if (count($page['column_1']) > 0) {
$obj = $page['column_1'][count($page['column_1']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][count($page['column_1']) - 1] = $obj;
}
if (count($page['column_2']) > 0) {
$obj = $page['column_2'][count($page['column_2']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_2'][count($page['column_2']) - 1] = $obj;
}
// Put the page onto pages and get a new one.
$pages[] = $page;
$page = get_new_pdf_page($recipe_data);
$line = 1;
}
// If you have more than 16 ingredients, use two columns.
if (count($col1) > 16) {
if ($line == 1) {
// The ingredients header.
$ingredient_heading = $recipe_data['ingredient_heading'];
$obj = "BT /F1 10 Tf 12 TL 20 185 Td ({$ingredient_heading})' ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][] = $obj;
// The 2-coloumn ingredients header underline.
$obj = "0.5 w\n20 170 m\n340 170 l\nS\n";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][] = $obj;
// Begin ingredients text for column 1.
$page['column_1'][] = "BT /F1 8 Tf 10 TL 25 170 Td ";
// Begin ingredients text for column 2.
$page['column_2'][] = "BT /F1 8 Tf 10 TL 170 170 Td ";
}
$page['column_1'][count($page['column_1']) - 1] .= $col1[$i++];
$page['column_2'][count($page['column_2']) - 1] .= $col1[$i];
}
else {
if ($line == 1) {
// The ingredients header.
$ingredient_heading = $recipe_data['ingredient_heading'];
$obj = "BT /F1 10 Tf 12 TL 20 185 Td ({$ingredient_heading})' ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][] = $obj;
// The 1-column ingredients header underline.
$obj = "0.5 w\n20 170 m\n155 170 l\nS\n";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][] = $obj;
$page['column_1'][] = "BT /F1 8 Tf 10 TL 25 170 Td ";
}
$page['column_1'][count($page['column_1']) - 1] .= $col1[$i];
}
$line++;
}
// Finish the last page.
if (count($page['column_1']) > 0) {
$obj = $page['column_1'][count($page['column_1']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][count($page['column_1']) - 1] = $obj;
}
if (count($page['column_2']) > 0) {
$obj = $page['column_2'][count($page['column_2']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_2'][count($page['column_2']) - 1] = $obj;
}
/*
* Handle the instructions.
*/
// $page still contains last page
$col = 0;
$col2_i_list = array();
// If column 2 is empty, use it.
if (count($page['column_2']) == 0) {
$col = 2;
$inst_list = wrap_and_pdf_escape($recipe_data['instructions'], 50);
}
else {
$col = 0;
$inst_list = wrap_and_pdf_escape($recipe_data['instructions'], 85);
// Put the page onto pages and get a new one.
$pages[] = $page;
$page = get_new_pdf_page($recipe_data);
}
$instruction_heading = $recipe_data['instruction_heading'];
if ($col == 0) {
// The instructions header.
$obj = "BT /F1 10 Tf 12 TL 20 185 Td ({$instruction_heading})' ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_0'][] = $obj;
// The instructions header underline.
$obj = "0.5 w\n20 170 m\n340 170 l\nS\n";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_0'][] = $obj;
}
elseif ($col == 2) {
// The instructions header.
$obj = "BT /F1 10 Tf 12 TL 165 185 Td ({$instruction_heading})' ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_2'][] = $obj;
// The instructions header underline.
$obj = "0.5 w\n165 170 m\n340 170 l\nS\n";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}endstream";
$page['column_2'][] = $obj;
}
$line = 1;
for ($i = 0; $i < count($inst_list); $i++) {
// You've filled the page, finish the columns and get a new page.
if ($line > 16) {
if (count($page['column_2']) > 0) {
$obj = $page['column_2'][count($page['column_2']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}endstream";
$page['column_2'][count($page['column_2']) - 1] = $obj;
}
if (count($page['column_0']) > 0) {
$obj = $page['column_0'][count($page['column_0']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}endstream";
$page['column_0'][count($page['column_0']) - 1] = $obj;
}
$pages[] = $page;
$page = get_new_pdf_page($recipe_data);
$line = 1;
$col = 0;
}
if ($line == 1) {
if ($col == 2) {
$page['column_2'][] = "BT /F1 8 Tf 10 TL 170 170 Td ";
}
elseif ($col == 0) {
$page['column_0'][] = "BT /F1 8 Tf 10 TL 20 170 Td ";
}
}
if ($col == 2) {
$page['column_2'][count($page['column_2']) - 1] .= $inst_list[$i];
}
elseif ($col == 0) {
$page['column_0'][count($page['column_0']) - 1] .= $inst_list[$i];
}
$line++;
}
// Finish the last page.
if (count($page['column_0']) > 0) {
$obj = $page['column_0'][count($page['column_0']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_0'][count($page['column_0']) - 1] = $obj;
}
if (count($page['column_1']) > 0) {
$obj = $page['column_1'][count($page['column_1']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_1'][count($page['column_1']) - 1] = $obj;
}
if (count($page['column_2']) > 0) {
$obj = $page['column_2'][count($page['column_2']) - 1];
$obj .= 'ET';
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['column_2'][count($page['column_2']) - 1] = $obj;
}
// Add remaining page to the pages array.
$pages[] = $page;
return $pages;
}
function get_new_pdf_page($recipe_data) {
$page = array(
'header' => array(),
'footer' => array(),
'column_0' => array(),
'column_1' => array(),
'column_2' => array(),
);
// The page header.
if (strlen($recipe_data['recipe_title']) < 48) {
$recipe_title = wrap_and_pdf_escape($recipe_data['recipe_title'], 48);
$recipe_title = join(" ", $recipe_title);
$obj = "BT /F1 14 Tf 12 TL 20 200 Td {$recipe_title} ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['header'][] = $obj;
}
else {
$recipe_title = wrap_and_pdf_escape($recipe_data['recipe_title'], 70);
$recipe_title = join(" ", $recipe_title);
$obj = "BT /F1 10 Tf 12 TL 20 200 Td {$recipe_title} ET";
$len = strlen($obj);
$obj = "<< /Length {$len} >>\nstream\n{$obj}\nendstream";
$page['header'][] = $obj;
}
return $page;
}
function wrap_and_pdf_escape($item_str, $wrap_cols, $wrap_indent = NULL) {
if (isset($wrap_indent)) {
$item_str = wordwrap($item_str, $wrap_cols, $wrap_indent);
}
else {
$item_str = wordwrap($item_str, $wrap_cols);
}
$item_str = str_replace(array(
"\\",
"(",
")",
), array(
"\\\\",
"\\(",
"\\)",
), $item_str);
$item_list = preg_split('/\\n/', $item_str);
for ($i = 0; $i < count($item_list); $i++) {
$item_list[$i] = str_replace("\r", " ", $item_list[$i]);
$item_list[$i] = "({$item_list[$i]})'\n";
}
return $item_list;
}
Functions
Name | Description |
---|---|
get_new_pdf_page | |
get_page_item_count | |
get_pdf_pages | |
prepare_receipe_data | |
recipe_pdf35_export_single | |
recipe_pdf35_recipeio | Implementation of hook_recipeio($type). |
wrap_and_pdf_escape |