Project

General

Profile

Paste
Download (32.5 KB) Statistics
| Branch: | Revision:

root / htmltest / sites / all / modules / views_pdf / views_pdf_template.php @ 011029ce

1
<?php
2

    
3
/**
4
 * @file
5
 * PDF Class to generate PDFs with native PHP. This class based on FPDF and FPDI.
6
 *
7
 * A direct include of this class is not realy possible. The basic functions of drupal must be
8
 * present.
9
 *
10
 */
11

    
12

    
13
/**
14
 * Get the depending classes.
15
 */
16
require_once views_pdf_get_library('tcpdf') . '/tcpdf.php';
17
require_once views_pdf_get_library('fpdi') . '/fpdi2tcpdf_bridge.php';
18
require_once views_pdf_get_library('fpdi') . '/fpdi.php';
19

    
20

    
21
/**
22
 * The main class to generate the PDF.
23
 */
24
class PdfTemplate extends FPDI {
25
  protected static $fontList = NULL;
26
  protected static $fontListClean = NULL;
27
  protected static $templateList = NULL;
28
  protected static $hyphenatePatterns = NULL;
29
  protected $defaultFontStyle = '';
30
  protected $defaultFontFamily = 'helvetica';
31
  protected $defaultFontSize = '11';
32
  protected $defaultTextAlign = 'L';
33
  protected $defaultFontColor = '000000';
34
  protected $defaultPageTemplateFiles = array();
35
  protected $mainContentPageNumber = 0;
36
  protected $rowContentPageNumber = 0;
37
  protected $defaultOrientation = 'P';
38
  protected $defaultFormat = 'A4';
39
  protected $addNewPageBeforeNextContent = FALSE;
40
  protected $elements = array();
41
  protected $headerFooterData = array();
42
  protected $views_header = '';
43
  protected $view = NULL;
44
  protected $views_footer = '';
45
  protected $headerFooterOptions = array();
46
  protected $lastWritingPage = 1;
47
  protected $lastWritingPositions;
48

    
49
  protected static $defaultFontList = array(
50
    'almohanad' => 'AlMohanad',
51
    'arialunicid0' => 'ArialUnicodeMS',
52
    'courier' => 'Courier',
53
    'courierb' => 'Courier Bold',
54
    'courierbi' => 'Courier Bold Italic',
55
    'courieri' => 'Courier Italic',
56
    'dejavusans' => 'DejaVuSans',
57
    'dejavusansb' => 'DejaVuSans-Bold',
58
    'dejavusansbi' => 'DejaVuSans-BoldOblique',
59
    'dejavusansi' => 'DejaVuSans-Oblique',
60
    'dejavusanscondensed' => 'DejaVuSansCondensed',
61
    'dejavusanscondensedb' => 'DejaVuSansCondensed-Bold',
62
    'dejavusanscondensedbi' => 'DejaVuSansCondensed-BoldOblique',
63
    'dejavusanscondensedi' => 'DejaVuSansCondensed-Oblique',
64
    'dejavusansmono' => 'DejaVuSansMono',
65
    'dejavusansmonob' => 'DejaVuSansMono-Bold',
66
    'dejavusansmonobi' => 'DejaVuSansMono-BoldOblique',
67
    'dejavusansmonoi' => 'DejaVuSansMono-Oblique',
68
    'dejavuserif' => 'DejaVuSerif',
69
    'dejavuserifb' => 'DejaVuSerif-Bold',
70
    'dejavuserifbi' => 'DejaVuSerif-BoldItalic',
71
    'dejavuserifi' => 'DejaVuSerif-Italic',
72
    'dejavuserifcondensed' => 'DejaVuSerifCondensed',
73
    'dejavuserifcondensedb' => 'DejaVuSerifCondensed-Bold',
74
    'dejavuserifcondensedbi' => 'DejaVuSerifCondensed-BoldItalic',
75
    'dejavuserifcondensedi' => 'DejaVuSerifCondensed-Italic',
76
    'freemono' => 'FreeMono',
77
    'freemonob' => 'FreeMonoBold',
78
    'freemonobi' => 'FreeMonoBoldOblique',
79
    'freemonoi' => 'FreeMonoOblique',
80
    'freesans' => 'FreeSans',
81
    'freesansb' => 'FreeSansBold',
82
    'freesansbi' => 'FreeSansBoldOblique',
83
    'freesansi' => 'FreeSansOblique',
84
    'freeserif' => 'FreeSerif',
85
    'freeserifb' => 'FreeSerifBold',
86
    'freeserifbi' => 'FreeSerifBoldItalic',
87
    'freeserifi' => 'FreeSerifItalic',
88
    'hysmyeongjostdmedium' => 'HYSMyeongJoStd-Medium-Acro',
89
    'helvetica' => 'Helvetica',
90
    'helveticab' => 'Helvetica Bold',
91
    'helveticabi' => 'Helvetica Bold Italic',
92
    'helveticai' => 'Helvetica Italic',
93
    'kozgopromedium' => 'KozGoPro-Medium-Acro',
94
    'kozminproregular' => 'KozMinPro-Regular-Acro',
95
    'msungstdlight' => 'MSungStd-Light-Acro',
96
    'stsongstdlight' => 'STSongStd-Light-Acro',
97
    'symbol' => 'Symbol',
98
    'times' => 'Times New Roman',
99
    'timesb' => 'Times New Roman Bold',
100
    'timesbi' => 'Times New Roman Bold Italic',
101
    'timesi' => 'Times New Roman Italic',
102
    'zapfdingbats' => 'Zapf Dingbats',
103
    'zarbold' => 'ZarBold'
104
  );
105

    
106
  /**
107
   * This method overrides the parent constructor method.
108
   * this is need to reset the default values.
109
   */
110
  public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=TRUE, $encoding='UTF-8', $diskcache=FALSE) {
111
    parent::__construct($orientation, $unit, $format, $unicode, $encoding, $diskcache);
112
    $this->defaultOrientation = $orientation;
113
    $this->defaultFormat = $format;
114
  }
115

    
116
  public function setDefaultFontSize($size) {
117
    $this->defaultFontSize = $size;
118
  }
119

    
120
  public function setDefaultFontFamily($family) {
121
    $this->defaultFontFamily = $family;
122
  }
123

    
124
  public function setDefaultFontStyle($style) {
125
    $this->defaultFontStyle = $style;
126
  }
127

    
128
  public function setDefaultTextAlign($style) {
129
    $this->defaultTextAlign = $style;
130
  }
131

    
132
  public function setDefaultFontColor($color) {
133
    $this->defaultFontColor = $color;
134
  }
135

    
136
  public function setDefaultPageTemplate($path, $key, $pageNumbering = 'main') {
137
    $this->defaultPageTemplateFiles[$key] = array(
138
      'path' => $path,
139
      'numbering' => $pageNumbering
140
    );
141
  }
142

    
143
  public function setViewsHeader($header) {
144
    $this->views_header = $header;
145
  }
146
  /**
147
   * This method must be overriden, in the other case, some
148
   * output is printed to the header.
149
   */
150
  function Header() {
151
    if (!empty($this->views_header)) {
152
      $this->writeHTML($this->views_header);
153
    }
154
  }
155

    
156
  public function setViewsFooter($footer) {
157
    $this->views_footer = $footer;
158
   }
159

    
160
  /**
161
   * This method must be overriden, in the other case, some
162
   * output is printed to the footer.
163
   */
164
  function Footer() {
165
    $this->SetY(-$this->bMargin);
166
    if (!empty($this->views_footer)) {
167
      $this->writeHTML($this->views_footer);
168
    }
169
  }
170

    
171
  /**
172
   * Converts a hex color into an array with RGB colors.
173
   */
174
  public function convertHexColorToArray($hex) {
175
    if (drupal_strlen($hex) == 6) {
176
      $r = drupal_substr($hex, 0, 2);
177
      $g = drupal_substr($hex, 2, 2);
178
      $b = drupal_substr($hex, 4, 2);
179
      return array(hexdec($r), hexdec($g), hexdec($b));
180

    
181
    }
182
    elseif (drupal_strlen($hex) == 3) {
183
      $r = drupal_substr($hex, 0, 1);
184
      $g = drupal_substr($hex, 1, 1);
185
      $b = drupal_substr($hex, 2, 1);
186
      return array(hexdec($r), hexdec($g), hexdec($b));
187

    
188
    }
189
    else {
190
      return array();
191
    }
192
  }
193

    
194
  /**
195
   * Parse color input into an array.
196
   *
197
   * @param string $color Color entered by the user
198
   * @return array color as an array
199
   */
200
  public function parseColor($color) {
201
    $color = trim($color, ', ');
202
    $components = explode(',', $color);
203
    if (count($components) == 1) {
204
      return $this->convertHexColorToArray($color);
205
    }
206
    else {
207
      // Remove white spaces from comonents:
208
      foreach ($components as $id => $component) {
209
        $components[$id] = trim($component);
210
      }
211
      return $components;
212
    }
213
  }
214

    
215
  /**
216
   * This method draws a field on the PDF.
217
   */
218
  public function drawContent($row, $options, &$view = NULL, $key = NULL, $printLabels = TRUE) {
219

    
220
    if (!is_array($options)) {
221
      $options = array();
222
    }
223

    
224
    // Set defaults:
225
    $options += array(
226
      'position' => array(),
227
      'text' => array(),
228
      'render' => array(),
229
    );
230

    
231
    $options['position'] += array(
232
      'corner' => 'top_left',
233
      'x' => 0,
234
      'y' => 0,
235
      'object' => 'last_position',
236
      'width' => 0,
237
      'height' => 0,
238
    );
239

    
240
    $options['text'] += array(
241
      'font_family' => 'default',
242
      'font_style' => '',
243
    );
244

    
245
    $options['render'] += array(
246
      'eval_before' => '',
247
      'eval_after' => '',
248
      'bypass_eval_before' => FALSE,
249
      'bypass_eval_after' => FALSE,
250
    );
251

    
252
    $x = $y = 0;
253

    
254

    
255
    // Get the page dimensions
256
    $pageDim = $this->getPageDimensions();
257

    
258
    // Check if there is a minimum space defined. If so, then ensure
259
    // that we have enough space left on this page. If not force adding
260
    // a new one.
261
    if (isset($options['render']['minimal_space'])) {
262
      $enoughtSpace = ($this->y + $this->bMargin + $options['render']['minimal_space']) < $pageDim['hk'];
263
    }
264
    else {
265
      $enoughtSpace = TRUE;
266
    }
267

    
268

    
269
    // Check if there is a page, if not add it:
270
    if (!$enoughtSpace OR $this->getPage() == 0 OR $this->addNewPageBeforeNextContent == TRUE) {
271
      $this->addNewPageBeforeNextContent = FALSE;
272
      $this->addPage();
273
    }
274

    
275
    // Get the page dimenstions again, because it can be that a new
276
    // page was added with new dimensions.
277
    $pageDim = $this->getPageDimensions();
278

    
279
    // Determine the last writting y coordinate, if we are on a new
280
    // page we need to reset it back to the top margin.
281
    if ($this->lastWritingPage != $this->getPage() OR ($this->y + $this->bMargin) > $pageDim['hk']) {
282
      $last_writing_y_position = $this->tMargin;
283
    }
284
    else {
285
      $last_writing_y_position = $this->y;
286
    }
287

    
288
    // Determin the x and y coordinates
289
    if ($options['position']['object'] == 'last_position') {
290
      $x = $this->x + $options['position']['x'];
291
      $y = $this->y + $options['position']['y'];
292
    }
293
    elseif ($options['position']['object'] == 'page') {
294
      switch ($options['position']['corner']) {
295
        default:
296
        case 'top_left':
297
          $x = $options['position']['x']+$this->lMargin;
298
          $y = $options['position']['y']+$this->tMargin;
299
          break;
300

    
301
        case 'top_right':
302
          $x = $options['position']['x'] + $pageDim['wk'] - $this->rMargin;
303
          $y = $options['position']['y'] + $this->tMargin;
304
          break;
305

    
306
        case 'bottom_left':
307
          $x = $options['position']['x'] + $this->rMargin;
308
          $y = $options['position']['y'] + $pageDim['hk'] - $this->bMargin;
309

    
310
          break;
311

    
312
        case 'bottom_right':
313
          $x = $options['position']['x'] + $pageDim['wk'] - $this->rMargin;
314
          $y = $options['position']['y'] + $pageDim['hk'] - $this->bMargin;
315

    
316
          break;
317
      }
318
    }
319
    elseif (
320
      $options['position']['object'] == 'self' or
321
      //$options['position']['object'] == 'last' or
322
      preg_match('/field\_(.*)/', $options['position']['object'], $rs)
323
    ) {
324
      if ($options['position']['object'] == 'last') {
325
        $relative_to_element = $this->lastWritingElement;
326
      }
327
      elseif ($options['position']['object'] == 'self') {
328
        $relative_to_element = $key;
329
      }
330
      else {
331
        $relative_to_element = $rs[1];
332
      }
333

    
334
      if (isset($this->elements[$relative_to_element])) {
335

    
336
        switch ($options['position']['corner']) {
337
          default:
338
          case 'top_left':
339
            $x = $options['position']['x'] + $this->elements[$relative_to_element]['x'];
340
            $y = $options['position']['y'] + $this->elements[$relative_to_element]['y'];
341
            break;
342

    
343
          case 'top_right':
344
            $x = $options['position']['x'] + $this->elements[$relative_to_element]['x'] + $this->elements[$relative_to_element]['width'];
345
            $y = $options['position']['y'] + $this->elements[$relative_to_element]['y'];
346
            break;
347

    
348
          case 'bottom_left':
349
            $x = $options['position']['x'] + $this->elements[$relative_to_element]['x'];
350
            $y = $options['position']['y'] + $this->elements[$relative_to_element]['y'] + $this->elements[$relative_to_element]['height'];
351

    
352
            break;
353

    
354
          case 'bottom_right':
355
            $x = $options['position']['x'] + $this->elements[$relative_to_element]['x'] + $this->elements[$relative_to_element]['width'];
356
            $y = $options['position']['y'] + $this->elements[$relative_to_element]['y'] + $this->elements[$relative_to_element]['height'];
357

    
358
            break;
359
        }
360

    
361
        // Handle if the relative element is on another page. So using the
362
        // the last writing position instead for y.
363
        if ($this->getPage() != $this->elements[$relative_to_element]['page'] && $options['position']['object'] != 'self') {
364
          $this->setPage($this->elements[$relative_to_element]['page']);
365
        }
366
        elseif ($this->getPage() != $this->elements[$relative_to_element]['page'] && $options['position']['object'] == 'self') {
367
          $y = $y - $this->elements[$relative_to_element]['y'] + $last_writing_y_position;
368
          $this->SetPage($this->lastWritingPage);
369
        }
370

    
371
      }
372
      else {
373
        $x = $this->x;
374
        $y = $last_writing_y_position;
375
      }
376

    
377
    }
378

    
379
    // No position match (for example header/footer)
380
    else {
381
      // Render or return
382
      if (is_object($view) && $key != NULL ) {
383
        $content = $view->field[$key]->theme($row);
384
      }
385
      else{
386
        return;
387
      }
388

    
389
    }
390

    
391
    if ($key !== NULL && $view->field[$key]->theme($row) || !empty($row)) {
392
      $this->SetX($x);
393
      $this->SetY($y);
394
      $this->renderRow($x, $y, $row, $options, $view, $key, $printLabels);
395
    }
396
  }
397

    
398
  protected function renderRow($x, $y, $row, $options, &$view = NULL, $key = NULL, $printLabels = TRUE) {
399

    
400
    $pageDim = $this->getPageDimensions();
401

    
402
    // Render the content if it is not already:
403
    if (is_object($view) && $key != NULL && isset($view->field[$key]) && is_object($view->field[$key])) {
404
      $content = $view->field[$key]->theme($row);
405
    }
406
    elseif (is_string($row)) {
407
      $content = $row;
408
    }
409
    else {
410
      // We got bad data. So return.
411
      return;
412
    }
413

    
414
    if (empty($key) || !empty($view->field[$key]->options['exclude']) || (empty($content) && $view->field[$key]->options['hide_empty'])) {
415
      return '';
416
    }
417

    
418
    // Apply the hyphenation patterns to the content:
419
    if (!isset($options['text']['hyphenate']) && is_object($view) && is_object($view->display_handler)) {
420
      $options['text']['hyphenate'] = $view->display_handler->get_option('default_text_hyphenate');
421
    }
422

    
423
    if (isset($options['text']['hyphenate']) && $options['text']['hyphenate'] != 'none') {
424
      $patternFile = $options['text']['hyphenate'];
425
      if ($options['text']['hyphenate'] == 'auto' && is_object($row)) {
426

    
427
        // Workaround:
428
        // Since "$nodeLanguage = $row->node_language;" does not work anymore,
429
        // we using this:
430
        if (isset($row->_field_data['nid']['entity']->language)) {
431
          $nodeLanguage = $row->_field_data['nid']['entity']->language;
432

    
433
          foreach (self::getAvailableHyphenatePatterns() as $file => $pattern) {
434
            if (stristr($pattern, $nodeLanguage) !== FALSE) {
435
              $patternFile = $file;
436
              break;
437
            }
438
          }
439
        }
440
      }
441

    
442
      $patternFile = views_pdf_get_library('tcpdf') . '/hyphenate_patterns/' . $patternFile;
443

    
444
      if (file_exists($patternFile)) {
445
        if (method_exists('TCPDF_STATIC', 'getHyphenPatternsFromTEX')) {
446
          $hyphen_patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patternFile);
447
        }
448
        else {
449
          $hyphen_patterns = $this->getHyphenPatternsFromTEX($patternFile);
450
        }
451

    
452
        // Bugfix if you like to print some html code to the PDF, we
453
        // need to prevent the replacement of this tags.
454
        $content = str_replace('&gt;', '&amp;gt;', $content);
455
        $content = str_replace('&lt;', '&amp;lt;', $content);
456
        $content = $this->hyphenateText($content, $hyphen_patterns);
457

    
458
      }
459
    }
460

    
461
    // Set css variable
462
    if (is_object($view) && is_object($view->display_handler)) {
463
      $css_file = $view->display_handler->get_option('css_file');
464
    }
465

    
466
    // Render Labels
467
    $prefix = '';
468
    if ($printLabels && !empty($view->field[$key]->options['label'])) {
469
      $prefix = $view->field[$key]->options['label'];
470
      if ($view->field[$key]->options['element_label_colon']) {
471
        $prefix .= ':';
472
      }
473
      $prefix .= ' ';
474
    }
475

    
476
    $font_size = empty($options['text']['font_size']) ? $this->defaultFontSize : $options['text']['font_size'] ;
477
    $font_family = ($options['text']['font_family'] == 'default' || empty($options['text']['font_family'])) ? $this->defaultFontFamily : $options['text']['font_family'];
478
    $font_style = is_array($options['text']['font_style']) ? $options['text']['font_style'] : $this->defaultFontStyle;
479
    $textColor = !empty($options['text']['color']) ? $this->parseColor($options['text']['color']) : $this->parseColor($this->defaultFontColor);
480

    
481

    
482
    $w = $options['position']['width'];
483
    $h = $options['position']['height'];
484
    $border = 0;
485
    $align = isset($options['text']['align']) ? $options['text']['align'] : $this->defaultTextAlign;
486
    $fill = 0;
487
    $ln = 1;
488
    $reseth = TRUE;
489
    $stretch = 0;
490
    $ishtml = isset($options['render']['is_html']) ? $options['render']['is_html'] : 1;
491
    $stripHTML = !$ishtml;
492
    $autopadding = TRUE;
493
    $maxh = 0;
494
    $valign = 'T';
495
    $fitcell = FALSE;
496

    
497
    // Run eval before.
498
    if (!empty($options['render']['bypass_eval_before']) && !empty($options['render']['eval_before'])) {
499
      eval($options['render']['eval_before']);
500
    }
501
    elseif (!empty($options['render']['eval_before']))  {
502
      $content = php_eval($options['render']['eval_before']);
503
    }
504

    
505
    // Add css if there is a css file set and stripHTML is not active.
506
    if (!empty($css_file) && is_string($css_file) && !$stripHTML && $ishtml && !empty($content)) {
507
      $content = '<link type="text/css" rel="stylesheet" media="all" href="' . $css_file . '" />' . PHP_EOL . $content;
508
    }
509

    
510
    // Set Text Color.
511
    $this->SetTextColorArray($textColor);
512

    
513
    // Set font.
514
    $this->SetFont($font_family, implode('', $font_style), $font_size);
515

    
516
    // Save the last page before starting writing, this
517
    // is needed to dected if we write over a page. Then we need
518
    // to reset the y coordinate for the 'last_writing' position option.
519
    $this->lastWritingPage = $this->getPage();
520

    
521
    if ($stripHTML) {
522
      $content = strip_tags($content);
523
    }
524

    
525
    // Write the content of a field to the pdf file:
526
    $this->MultiCell($w, $h, $prefix . $content, $border, $align, $fill, $ln, $x, $y, $reseth, $stretch, $ishtml, $autopadding, $maxh, $valign, $fitcell);
527

    
528
    // Reset font to default.
529
    $this->SetFont($this->defaultFontFamily, implode('', $this->defaultFontStyle), $this->defaultFontSize);
530

    
531
    // Run eval after.
532
    if (!empty($options['render']['bypass_eval_after']) && !empty($options['render']['eval_after'])) {
533
      eval($options['render']['eval_after']);
534
    }
535
    elseif (!empty($options['render']['eval_after'])) {
536
      $content = php_eval($options['render']['eval_after']);
537
    }
538

    
539
    // Write Coordinates of element.
540
    $this->elements[$key] = array(
541
      'x' => $x,
542
      'y' => $y,
543
      'width' => empty($w) ? ($pageDim['wk'] - $this->rMargin-$x) : $w,
544
      'height' => $this->y - $y,
545
      'page' => $this->lastWritingPage,
546
    );
547

    
548
    $this->lastWritingElement = $key;
549
  }
550

    
551
  /**
552
   * This method draws a table on the PDF.
553
   */
554
  public function drawTable(&$view, $options) {
555

    
556
    $rows = $view->result;
557
    $columns = $view->field;
558
    $pageDim = $this->getPageDimensions();
559

    
560
    // Set draw point to the indicated position:
561
    if (empty($options['position']['x'])) {
562
      $options['position']['x'] = 0;
563
    }
564

    
565
    if (empty($options['position']['y'])) {
566
      $options['position']['y'] = 0;
567
    }
568

    
569
    if (isset($options['position']['last_writing_position']) && $options['position']['last_writing_position']) {
570
      $y = $options['position']['y'] + $this->y;
571
      $x = $options['position']['x'] + $this->x;
572
    }
573
    else {
574
      $y = $options['position']['y'];
575
      $x = $options['position']['x'];
576
    }
577

    
578
    if (isset($options['position']['width']) && !empty($options['position']['width'])) {
579
      $width = $options['position']['width'];
580
    }
581
    else {
582
      $width = $pageDim['wk'] - $this->rMargin - $x;
583
    }
584

    
585
    $sumWidth = 0;
586
    $numberOfColumnsWithoutWidth = 0;
587

    
588
    // Set the definitiv width of a column
589
    foreach ($columns as $id => $columnName) {
590
      if (isset($options['info'][$id]['position']['width']) && !empty($options['info'][$id]['position']['width'])) {
591
        $sumWidth += $options['info'][$id]['position']['width'];
592
      }
593
      else {
594
        $numberOfColumnsWithoutWidth++;
595
      }
596
    }
597
    if ($numberOfColumnsWithoutWidth > 0) {
598
      $defaultColumnWidth = ($width - $sumWidth) / $numberOfColumnsWithoutWidth;
599
    }
600
    else {
601
      $defaultColumnWidth = 0;
602
    }
603

    
604
    // Print header:
605
    $rowX = $x;
606
    $page = $this->getPage();
607
    if ($page == 0) {
608
      $this->addPage();
609
      $page = $this->getPage();
610
    }
611

    
612
    if (!isset($options['position']['row_height']) || empty($options['position']['row_height'])) {
613
      $options['position']['row_height'] = 0;
614
    }
615

    
616
    foreach ($columns as $id => $column) {
617

    
618
      if (!empty($column->options['exclude'])) {
619
        continue;
620
      }
621

    
622
      if (!is_array($options['info'][$id])) {
623
        $options['info'][$id] = array();
624
      }
625

    
626
      $options['info'][$id] += array(
627
        'header_style' => array(),
628
        'body_style' => array(),
629
      );
630

    
631
      $options['info'][$id]['header_style'] += array(
632
        'position' => array(),
633
        'text' => array(),
634
        'render' => array(),
635
      );
636

    
637
      $options['info'][$id]['header_style']['position'] += array(
638
        'corner' => 'top_left',
639
        'x' => NULL,
640
        'y' => NULL,
641
        'object' => '',
642
        'width' => NULL,
643
        'height' => NULL,
644
      );
645

    
646
      $options['info'][$id]['header_style']['text'] += array(
647
        'font_family' => 'default',
648
        'font_style' => '',
649
      );
650

    
651
      $options['info'][$id]['header_style']['text'] += array(
652
        'eval_before' => '',
653
        'eval_after' => '',
654
      );
655

    
656
      $options['info'][$id]['body_style'] += array(
657
        'position' => array(),
658
        'text' => array(),
659
        'render' => array(),
660
      );
661

    
662
      $options['info'][$id]['body_style']['position'] += array(
663
        'corner' => 'top_left',
664
        'x' => NULL,
665
        'y' => NULL,
666
        'object' => '',
667
        'width' => NULL,
668
        'height' => NULL,
669
      );
670

    
671
      $options['info'][$id]['body_style']['text'] += array(
672
        'font_family' => 'default',
673
        'font_style' => '',
674
      );
675

    
676
      $options['info'][$id]['body_style']['text'] += array(
677
        'eval_before' => '',
678
        'eval_after' => '',
679
      );
680

    
681
      $headerOptions = $options['info'][$id]['header_style'];
682

    
683
      if (isset($options['info'][$id]['position']['width']) && !empty($options['info'][$id]['position']['width'])) {
684
        $headerOptions['position']['width'] = $options['info'][$id]['position']['width'];
685
      }
686
      else {
687
        $headerOptions['position']['width'] = $defaultColumnWidth;
688
      }
689
      $headerOptions['position']['object'] = 'last_position_without_reset';
690

    
691
      $this->SetY($y);
692
      $this->SetX($x);
693
      $this->setPage($page);
694

    
695
      $this->renderRow($x, $y, $column->options['label'], $headerOptions);
696
      $x += $headerOptions['position']['width'];
697
    }
698

    
699
    $rowY = $this->y;
700

    
701
    if (!isset($options['position']['row_height']) || empty($options['position']['row_height'])) {
702
      $options['position']['row_height'] = 0;
703
    }
704

    
705
    foreach ($rows as $row) {
706
      $x = $rowX;
707

    
708
       // Get the page dimensions
709
      $pageDim = $this->getPageDimensions();
710

    
711
      if (($rowY + $this->bMargin + $options['position']['row_height']) > $pageDim['hk']) {
712
        $rowY = $this->tMargin;
713
        $this->addPage();
714
      }
715

    
716
      if ($this->lastWritingPage != $this->getPage()) {
717
        $rowY = $this->y; // $rowY - $pageDim['hk']
718
      }
719

    
720
      $y = $rowY;
721
      $page = $this->getPage();
722
      foreach ($columns as $id => $column) {
723

    
724
        if (!empty($column->options['exclude']) && is_object($view->field[$id])) {
725
          // Render the element, but dont print the output. This
726
          // is required to allow the use of tokens in other fields.
727
          $view->field[$id]->theme($row);
728
          continue;
729
        }
730

    
731
        $bodyOptions = $options['info'][$id]['body_style'];
732

    
733
        if (isset($options['info'][$id]['position']['width']) && !empty($options['info'][$id]['position']['width'])) {
734
          $bodyOptions['position']['width'] = $options['info'][$id]['position']['width'];
735
        }
736
        else {
737
          $bodyOptions['position']['width'] = $defaultColumnWidth;
738
        }
739
        $bodyOptions['position']['object'] = 'last_position';
740

    
741
        $this->setPage($page);
742
        $this->SetY($y);
743
        $this->SetX($x);
744

    
745
        $bodyOptions['position']['height'] = 0;
746

    
747
        $this->renderRow($x, $y, $row, $bodyOptions, $view, $id, FALSE);
748

    
749
        $x += $bodyOptions['position']['width'];
750

    
751
        // If the cell is writting over the row, we need to adjust the
752
        // row y position.
753
        if (($rowY + $options['position']['row_height']) < $this->y) {
754
          $rowY = $this->y - $options['position']['row_height'];
755
        }
756

    
757
      }
758

    
759
      $rowY += $options['position']['row_height'];
760

    
761
    }
762

    
763
    $this->SetY($rowY + $options['position']['row_height']);
764
  }
765

    
766
  /**
767
   * This method adds a existing PDF document to the current document. If
768
   * the file does not exists this method will return 0. In all other cases
769
   * it will returns the number of the added pages.
770
   *
771
   * @param $path string Path to the file
772
   * @return integer Number of added pages
773
   */
774
  public function addPdfDocument($path) {
775
    if (empty($path) || !file_exists($path)) {
776
      return 0;
777
    }
778

    
779
    $numberOfPages = $this->setSourceFile($path);
780
    for ($i = 1; $i <= $numberOfPages; $i++) {
781

    
782
      $dim = $this->getTemplateSize($i);
783
      $format[0] = $dim['w'];
784
      $format[1] = $dim['h'];
785

    
786
      if ($dim['w'] > $dim['h']) {
787
        $orientation = 'L';
788
      }
789
      else {
790
        $orientation = 'P';
791
      }
792
      $this->setPageFormat($format, $orientation);
793
      parent::addPage();
794

    
795
      // Ensure that all new content is printed to a new page
796
      $this->y = 0;
797

    
798
      $page = $this->importPage($i);
799
      $this->useTemplate($page, 0, 0);
800
      $this->addNewPageBeforeNextContent = TRUE;
801
    }
802

    
803
    return $numberOfPages;
804

    
805
  }
806

    
807
  /**
808
   * This method resets the page number. This is useful if you want to start
809
   * the numbering by zero.
810
   */
811
  public function resetRowPageNumber() {
812
    $this->rowContentPageNumber = 0;
813
  }
814

    
815
  /**
816
   * This method adds a new page to the PDF.
817
   */
818
  public function addPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false, $path = NULL, $reset = FALSE, $numbering = 'main') {
819

    
820
    // Do not add any new page, if we are writing
821
    // in the footer or header.
822
    if ($this->InFooter) {
823
      return;
824
    }
825

    
826
    $this->mainContentPageNumber++;
827
    $this->rowContentPageNumber++;
828

    
829
    // Prevent a reset without any template
830
    if ($reset == TRUE && (empty($path) || !file_exists($path))) {
831
      parent::addPage();
832
      $this->setPageFormat($this->defaultFormat, $this->defaultOrientation);
833
      return;
834
    }
835

    
836
    $files = $this->defaultPageTemplateFiles;
837

    
838
    // Reset with new template
839
    if ($reset) {
840
      $files = array();
841
    }
842

    
843
    if ($path != NULL) {
844
      $files[] = array('path' => $path, 'numbering' => $numbering);
845
    }
846
    $format = FALSE;
847
    foreach ($files as $file) {
848
      if (!empty($file['path']) && file_exists($file['path'])) {
849
        $path = realpath($file['path']);
850

    
851
        $numberOfPages = $this->setSourceFile($path);
852
        if ($file['numbering'] == 'row')  {
853
          $index = min($this->rowContentPageNumber, $numberOfPages);
854
        }
855
        else {
856
          $index = min($this->mainContentPageNumber, $numberOfPages);
857
        }
858

    
859

    
860
        $page = $this->importPage($index);
861

    
862
        // ajust the page format (only for the first template)
863
        if ($format == FALSE) {
864

    
865
          $dim = $this->getTemplateSize($index);
866
          $format[0] = $dim['w'];
867
          $format[1] = $dim['h'];
868
          //$this->setPageFormat($format);
869
          if ($dim['w'] > $dim['h']) {
870
            $orientation = 'L';
871
          }
872
          else {
873
            $orientation = 'P';
874
          }
875
          $this->setPageFormat($format, $orientation);
876
          parent::addPage();
877
        }
878

    
879
        // Apply the template
880
        $this->useTemplate($page, 0, 0);
881
      }
882
    }
883

    
884
    // if all paths were empty, ensure that at least the page is added
885
    if ($format == FALSE) {
886
      parent::addPage();
887
      $this->setPageFormat($this->defaultFormat, $this->defaultOrientation);
888
    }
889

    
890
  }
891

    
892
  /**
893
   * Sets the current header and footer of the page.
894
   */
895
  public function setHeaderFooter($record, $options, $view) {
896
    //if ($this->getPage() > 0 && !isset($this->headerFooterData[$this->getPage()])) {
897
      $this->headerFooterData[$this->getPage()] = $record;
898
    //}
899
    $this->headerFooterOptions = $options;
900
    $this->view =& $view;
901
  }
902

    
903
  /**
904
   * Close the document. This is called automatically by
905
   * TCPDF::Output().
906
   */
907
  public function Close() {
908
    // Print the Header & Footer
909
    for ($page = 1; $page <= $this->getNumPages(); $page++) {
910
      $this->setPage($page);
911

    
912
      if (isset($this->headerFooterData[$page])) {
913
        $record = $this->headerFooterData[$page];
914
        if (is_array($this->headerFooterOptions['formats'])) {
915
          foreach ($this->headerFooterOptions['formats'] as $id => $options) {
916
            if ($options['position']['object'] == 'header_footer') {
917
              $fieldOptions = $options;
918
              $fieldOptions['position']['object'] = 'page';
919
              $this->InFooter = TRUE;
920

    
921
              // backup margins
922
              $ml = $this->lMargin;
923
              $mr = $this->rMargin;
924
              $mt = $this->tMargin;
925
              $this->SetMargins(0, 0, 0);
926

    
927
              $this->drawContent($record, $fieldOptions, $this->view, $id);
928
              $this->InFooter = FALSE;
929

    
930
              // restore margins
931
              $this->SetMargins($ml, $mt, $mr);
932
            }
933
          }
934
        }
935
      }
936
    }
937

    
938
    // call parent:
939
    parent::Close();
940

    
941
  }
942

    
943
  /**
944
   * This method returns a list of current uploaded files.
945
   */
946
  public static function getAvailableTemplates() {
947
    if (self::$templateList != NULL) {
948
      return self::$templateList;
949
    }
950

    
951
    $files_path = drupal_realpath('public://');
952
    $template_dir = variable_get('views_pdf_template_path', 'views_pdf_templates');
953
    $dir = $files_path . '/' . $template_dir;
954
    $templatesFiles = file_scan_directory($dir, '/.pdf$/', array('nomask' => '/(\.\.?|CVS)$/'), 1);
955

    
956
    $templates = array();
957

    
958
    foreach ($templatesFiles as $file) {
959
      $templates[$file->filename] = $file->name;
960
    }
961

    
962
    self::$templateList = $templates;
963

    
964
    return $templates;
965

    
966
  }
967

    
968
  /**
969
   * This method returns the path to a specific template.
970
   */
971
  public static function getTemplatePath($template, $row = NULL, $view = NULL) {
972
    if (empty($template)) {
973
      return '';
974
    }
975

    
976
    if ($row != NULL && $view != NULL && !preg_match('/\.pdf/', $template)) {
977
      return drupal_realpath($row->field_data_field_file_node_values[0]['uri']);
978
    }
979

    
980
    $template_dir = variable_get('views_pdf_template_stream', 'public://views_pdf_templates');
981
    return drupal_realpath($template_dir . '/' . $template);
982
  }
983

    
984
  /**
985
   * This method returns a list of available fonts.
986
   */
987
  public static function getAvailableFonts() {
988
    if (self::$fontList != NULL) {
989
      return self::$fontList;
990
    }
991

    
992
    // Get all pdf files with the font list: K_PATH_FONTS
993
    $fonts = file_scan_directory(K_PATH_FONTS, '/.php$/', array('nomask' => '/(\.\.?|CVS)$/', 'recurse' => FALSE), 1);
994
    $cache = cache_get('views_pdf_cached_fonts');
995

    
996
    $cached_font_mapping = NULL;
997

    
998
    if (is_object($cache)) {
999
      $cached_font_mapping = $cache->data;
1000
    }
1001

    
1002
    if (is_array($cached_font_mapping)) {
1003
      $font_mapping = array_merge(self::$defaultFontList, $cached_font_mapping);
1004
    }
1005
    else {
1006
      $font_mapping = self::$defaultFontList;
1007
    }
1008

    
1009
    foreach ($fonts as $font) {
1010
        $name = self::getFontNameByFileName($font->uri);
1011
        if (isset($name)) {
1012
          $font_mapping[$font->name] = $name;
1013
        }
1014
    }
1015

    
1016
    asort($font_mapping);
1017

    
1018
    cache_set('views_pdf_cached_fonts', $font_mapping);
1019

    
1020
    // Remove all fonts without name
1021
    foreach ($font_mapping as $key => $font) {
1022
      if (empty($font)) {
1023
        unset($font_mapping[$key]);
1024
      }
1025

    
1026
    }
1027

    
1028
    self::$fontList = $font_mapping;
1029

    
1030
    return $font_mapping;
1031
  }
1032

    
1033
  /**
1034
   * This method returns a cleaned up version of the font list.
1035
   */
1036
  public static function getAvailableFontsCleanList() {
1037
    if (self::$fontListClean != NULL) {
1038
      return self::$fontListClean;
1039
    }
1040

    
1041
    $clean = self::getAvailableFonts();
1042

    
1043
    foreach ($clean as $key => $font) {
1044

    
1045
      // Unset bold, italic, italic/bold fonts
1046
      unset($clean[ ($key . 'b') ]);
1047
      unset($clean[ ($key . 'bi') ]);
1048
      unset($clean[ ($key . 'i') ]);
1049

    
1050
    }
1051

    
1052
    self::$fontListClean = $clean;
1053

    
1054
    return $clean;
1055
  }
1056

    
1057
  /**
1058
   * This method returns a list of hyphenation patterns, that are
1059
   * available.
1060
   */
1061
  public static function getAvailableHyphenatePatterns() {
1062
    if (self::$hyphenatePatterns != NULL) {
1063
      return self::$hyphenatePatterns;
1064
    }
1065

    
1066
    self::$hyphenatePatterns = array();
1067

    
1068
    $files = file_scan_directory(views_pdf_get_library('tcpdf') . '/hyphenate_patterns', '/.tex$/', array('nomask' => '/(\.\.?|CVS)$/'), 1);
1069

    
1070
    foreach ($files as $file) {
1071
      self::$hyphenatePatterns[basename($file->uri)] = str_replace('hyph-', '', $file->name);
1072
    }
1073

    
1074

    
1075
    return self::$hyphenatePatterns;
1076
  }
1077

    
1078
  /**
1079
   * This method returns the name of a given font.
1080
   */
1081
  protected static function getFontNameByFileName($path) {
1082
    include $path;
1083
    if (isset($name)) {
1084
      return $name;
1085
    }
1086
    else {
1087
      return NULL;
1088
    }
1089
  }
1090
}