PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Document/src/document/pdf/renderer/main.php

https://github.com/Yannix/zetacomponents
PHP | 933 lines | 381 code | 81 blank | 471 comment | 21 complexity | a9d3ea11009c99b0e2387d88e3e1b8c6 MD5 | raw file
  1. <?php
  2. /**
  3. * File containing the ezcDocumentPdfMainRenderer class
  4. *
  5. * Licensed to the Apache Software Foundation (ASF) under one
  6. * or more contributor license agreements. See the NOTICE file
  7. * distributed with this work for additional information
  8. * regarding copyright ownership. The ASF licenses this file
  9. * to you under the Apache License, Version 2.0 (the
  10. * "License"); you may not use this file except in compliance
  11. * with the License. You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing,
  16. * software distributed under the License is distributed on an
  17. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  18. * KIND, either express or implied. See the License for the
  19. * specific language governing permissions and limitations
  20. * under the License.
  21. *
  22. * @package Document
  23. * @version //autogen//
  24. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
  25. * @access private
  26. */
  27. /**
  28. * Main PDF renderer class, dispatching to sub renderer, maintaining page
  29. * contexts and transactions.
  30. *
  31. * The basic principles behind the used stacked backtracking rendering
  32. * algorithm are explained below.
  33. *
  34. * The basics
  35. * ==========
  36. *
  37. * The rendering size of a single block (paragraph, image) cannot be guessed
  38. * properly beforehand, because the dimensions depend on the associated styles
  39. * and the driver which needs to render the styles. Because of different fonts
  40. * the size of a single simple string may notably vary. The renderer do not
  41. * know about font properties, but only the drivers do.
  42. *
  43. * Because of that the renderers (like the paragraph renderer) can only
  44. * request the used dimensions for each word, or word part from the current
  45. * driver and try to fit that word into the currently available space.
  46. *
  47. * Some general constraints, like the handling of orphans and widows, require
  48. * the renderer to backtrack. If a orphans constraint could not be fulfilled
  49. * with the first rendering try, the renderer needs to decide to render less
  50. * lines on the prior page and therefore needs to revert all local rendering
  51. * steps and retry the rendering with the additional knowledge.
  52. *
  53. * For widow constraints this may mean, that a full paragraph is moved to the
  54. * next page, which could mean, that the title before that paragraph might
  55. * also be relocated to the following page, which would mean to revert
  56. * multiple renderers and elements. To make this possible the renderer wraps
  57. * the driver in a transactional driver wrapper.
  58. *
  59. * The transactional driver wrapper
  60. * --------------------------------
  61. *
  62. * Like the last paragraph explained it might be necessary to revert (large)
  63. * amounts of rendering operations. Once the rendering operations (like
  64. * drawWord) hit the driver they are immediately serialized into the
  65. * respective output format (PDF), and could not be reverted anymore.
  66. *
  67. * So an additional layer has been implemented in the class
  68. * ezcDocumentPdfTransactionalDriverWrapper, which implements the same
  69. * interface as all the other drivers, as well as some additional methods to
  70. * handle "transactions".
  71. *
  72. * A renderer, like the paragraph renderer, may start a transaction, receives
  73. * an ID identifying the started transaction, and may then start its rendering
  74. * operations. If the rendering reached a dead end, it may revert everything
  75. * using the initially given transaction ID. The revert will affect all
  76. * operations since the original call to startTransaction(), even if other
  77. * sub-renderers also started transactions in the meantime.
  78. *
  79. * The logged calls to the driver are passed up to the real driver once save()
  80. * is called explicitly for the given transaction ID, or the main renderer
  81. * attempts to write the PDF into a file.
  82. *
  83. * Depending on the type of the call the driver wrapper logs and / or passes
  84. * the call directly up to the actual driver.
  85. *
  86. * Calls which are logged only:
  87. * - Everything performing actual rendering, like drawLine(), drawWord(), ...
  88. *
  89. * Calls which are logged and passed:
  90. * - Everything setting the current style configuration, which might also be
  91. * relevant for font width estimation, especially: setStyle()
  92. *
  93. * Calls which are not logged, but passed:
  94. * - Everything, which only requests properties, but does not change the
  95. * driver state, like getTextWidth()
  96. *
  97. * The stacked renderers
  98. * ---------------------
  99. *
  100. * The main renderer, which is defined in this class, is responsible for
  101. * managing the pages, the available horizontal space on the current page and
  102. * calling the sub renderers for the distinct parts in the Docbook document.
  103. *
  104. * For each part there is a specialized renderer, which is only responsible
  105. * for rendering such a part, like a list renderer, a list item renderer or a
  106. * paragraph renderer. The main renderer traverses the Docbook document and calls
  107. * the appropriate renderer. You may register additional renderers with the
  108. * main renderer, for your custom elements, or overwrite the defined default
  109. * renderers.
  110. *
  111. * The main renderer also handles special page elements, like headers and
  112. * footers for each page.
  113. *
  114. * The sub renderers ask the main renderer for new space, if they exceeded the
  115. * available space in the current column / on the current page. This is
  116. * implemented in the method getNextRenderingPosition(). This method might
  117. * request a new page from the driver.
  118. *
  119. * The sub renderer may as well call other sub renderer, for stacked element
  120. * definitions or may request rendering for all those elements by the main
  121. * renderer calling back to the process() method.
  122. *
  123. * The table sub renderer
  124. * ----------------------
  125. *
  126. * The table renderer is a special sub renderer, since the common space
  127. * estimation does not apply here. Tables are structured into cells and the
  128. * elements contained in one cell may only use the space defined by the cell.
  129. * The table renderer therefore mimics (and extends) the main renderer. So
  130. * when the contents of one cell are rendered the sub renderers for the cell
  131. * contents (paragraphs, lists, ...) receive an instance of the table renderer
  132. * as their "new" main renderer. The table renderer overwrites the methods
  133. * like process() and getNextRenderingPosition(), so the sub renderers render
  134. * their stuff at the correct positions in the cell.
  135. *
  136. * The table renderer itself again dispatches to its main renderer, when, for
  137. * example, allocating new pages. In case of a stacked table, the main
  138. * renderer of a table renderer may again be a table renderer, which then
  139. * dispatches to the original main renderer.
  140. *
  141. * Style inheritance
  142. * -----------------
  143. *
  144. * The definition of styles works just like CSS with HTML. Each element
  145. * inherits the styles from its parent element, which are then overwritten by
  146. * the defined styles in the (P)CSS file.
  147. *
  148. * The inferring of the styles for a given element is implemented in the
  149. * ezcDocumentPcssStyleInferencer class. An instance of this class containing
  150. * the currently defined styles is available during the whole rendering
  151. * process and will provide the styles for any element, which is passed to the
  152. * object.
  153. *
  154. * Hyphenation
  155. * -----------
  156. *
  157. * Hyphenation is a critical task for proper text rendering. A custom
  158. * hyphenator may be defined and passed to the renderer. Each text renderer
  159. * will the ask the hyphenator to split words, if the whole word does not fit
  160. * into one line any more. It would be sensible to implement a hyphenator
  161. * based on some available dictionary files.
  162. *
  163. * Tokenizer
  164. * ---------
  165. *
  166. * For some languages it might be necessary to implement a different text
  167. * tokenizer, which does not just split words at whitespaces. To accomplish
  168. * that you may implement and pass a custom tokenizer, which is the
  169. * responsible for splitting texts.
  170. *
  171. * Some renderers, like the literal box renderer, may already use custom
  172. * tokenizers, to implement special rendering tasks.
  173. *
  174. * @package Document
  175. * @access private
  176. * @version //autogen//
  177. */
  178. class ezcDocumentPdfMainRenderer extends ezcDocumentPdfRenderer implements ezcDocumentErrorReporting
  179. {
  180. /**
  181. * Hyphenator used to split up words
  182. *
  183. * @var ezcDocumentPdfHyphenator
  184. */
  185. protected $hyphenator;
  186. /**
  187. * Tokenizer used to split up strings into words
  188. *
  189. * @var ezcDocumentPdfTokenizer
  190. */
  191. protected $tokenizer;
  192. /**
  193. * Document to render
  194. *
  195. * @var ezcDocumentDocbook
  196. */
  197. protected $document;
  198. /**
  199. * Last transactions started before rendering a new title. This is used to
  200. * determine, if a title is positioned as a single item in a column or on a
  201. * page and switch it to the next page in this case.
  202. *
  203. * @var mixed
  204. */
  205. protected $titleTransaction = null;
  206. /**
  207. * Indicator to restart rendering with an earlier item on the same level in
  208. * the DOM document tree.
  209. *
  210. * @var mixed
  211. */
  212. protected $restart = false;
  213. /**
  214. * Errors occured during the conversion process
  215. *
  216. * @var array
  217. */
  218. protected $errors = array();
  219. /**
  220. * Maps document elements to handler functions
  221. *
  222. * Maps each document element of the associated namespace to its handler
  223. * method in the current class.
  224. *
  225. * @var array
  226. */
  227. protected $handlerMapping = array(
  228. 'http://docbook.org/ns/docbook' => array(
  229. 'article' => 'initializeDocument',
  230. 'section' => 'renderBlock',
  231. 'sectioninfo' => 'appendMetaData',
  232. 'para' => 'renderParagraph',
  233. 'title' => 'renderTitle',
  234. 'mediaobject' => 'renderMediaObject',
  235. 'literallayout' => 'renderLiteralLayout',
  236. 'blockquote' => 'renderBlockquote',
  237. 'table' => 'renderTable',
  238. 'itemizedlist' => 'renderList',
  239. 'orderedlist' => 'renderList',
  240. 'variablelist' => 'renderBlock',
  241. 'varlistentry' => 'renderBlock',
  242. 'listitem' => 'renderListItem',
  243. 'term' => 'renderTitle',
  244. ),
  245. );
  246. /**
  247. * Additional PDF parts.
  248. *
  249. * @var array
  250. */
  251. protected $parts = array();
  252. /**
  253. * Error reporting level
  254. *
  255. * @var int
  256. */
  257. protected $errorReporting = 15;
  258. /**
  259. * PDF renderer options
  260. *
  261. * @var ezcDocumentPdfOptions
  262. */
  263. protected $options;
  264. /**
  265. * Construct renderer from driver to use
  266. *
  267. * @param ezcDocumentPdfDriver $driver
  268. * @param ezcDocumentPcssStyleInferencer $styles
  269. * @param ezcDocumentPdfOptions $options
  270. * @return void
  271. */
  272. public function __construct( ezcDocumentPdfDriver $driver, ezcDocumentPcssStyleInferencer $styles, ezcDocumentPdfOptions $options = null )
  273. {
  274. $this->driver = new ezcDocumentPdfTransactionalDriverWrapper();
  275. $this->driver->setDriver( $driver );
  276. $this->styles = $styles;
  277. $this->options = $options;
  278. $this->errorReporting = $options !== null ? $options->errorReporting : 15;
  279. }
  280. /**
  281. * Trigger visitor error
  282. *
  283. * Emit a vistitor error, and convert it to an exception depending on the
  284. * error reporting settings.
  285. *
  286. * @param int $level
  287. * @param string $message
  288. * @param string $file
  289. * @param int $line
  290. * @param int $position
  291. * @return void
  292. */
  293. public function triggerError( $level, $message, $file = null, $line = null, $position = null )
  294. {
  295. if ( $level & $this->errorReporting )
  296. {
  297. throw new ezcDocumentVisitException( $level, $message, $file, $line, $position );
  298. }
  299. else
  300. {
  301. // If the error should not been reported, we aggregate it to maybe
  302. // display it later.
  303. $this->errors[] = new ezcDocumentVisitException( $level, $message, $file, $line, $position );
  304. }
  305. }
  306. /**
  307. * Return list of errors occured during visiting the document.
  308. *
  309. * May be an empty array, if on errors occured, or a list of
  310. * ezcDocumentVisitException objects.
  311. *
  312. * @return array
  313. */
  314. public function getErrors()
  315. {
  316. return $this->errors;
  317. }
  318. /**
  319. * Tries to locate a file
  320. *
  321. * Tries to locate a file, referenced in a docbook document. If available
  322. * the document path is used a base for relative paths.
  323. *
  324. * @param string $file
  325. * @return string
  326. */
  327. public function locateFile( $file )
  328. {
  329. if ( !ezcBaseFile::isAbsolutePath( $file ) )
  330. {
  331. $file = $this->document->getPath() . $file;
  332. }
  333. if ( !is_file( $file ) )
  334. {
  335. throw new ezcBaseFileNotFoundException( $file );
  336. }
  337. return $file;
  338. }
  339. /**
  340. * Register an additional PDF part
  341. *
  342. * Register additional parts, like footnotes, headers or title pages.
  343. *
  344. * @param ezcDocumentPdfPart $part
  345. * @return void
  346. */
  347. public function registerPdfPart( ezcDocumentPdfPart $part )
  348. {
  349. $this->parts[] = $part;
  350. $part->registerContext( $this, $this->driver, $this->styles );
  351. }
  352. /**
  353. * Render given document
  354. *
  355. * Returns the rendered PDF as string
  356. *
  357. * @param ezcDocumentDocbook $document
  358. * @param ezcDocumentPdfHyphenator $hyphenator
  359. * @param ezcDocumentPdfTokenizer $tokenizer
  360. * @return string
  361. */
  362. public function render( ezcDocumentDocbook $document, ezcDocumentPdfHyphenator $hyphenator = null, ezcDocumentPdfTokenizer $tokenizer = null )
  363. {
  364. $this->hyphenator = $hyphenator !== null ? $hyphenator : new ezcDocumentPdfDefaultHyphenator();
  365. $this->tokenizer = $tokenizer !== null ? $tokenizer : new ezcDocumentPdfDefaultTokenizer();
  366. $this->document = $document;
  367. // Register custom fonts in driver
  368. $this->registerFonts();
  369. // Inject custom element class, for style inferencing
  370. $dom = $document->getDomDocument();
  371. // Reload the XML document with to a DOMDocument with a custom element
  372. // class. Just registering it on the existing document seems not to
  373. // work in all cases.
  374. $reloaded = new DOMDocument();
  375. $reloaded->registerNodeClass( 'DOMElement', 'ezcDocumentLocateableDomElement' );
  376. $reloaded->loadXml( $dom->saveXml() );
  377. $this->process( $reloaded );
  378. return $this->driver->save();
  379. }
  380. /**
  381. * Register fonts in driver
  382. *
  383. * Register the font classes specified in the styles with the driver, so
  384. * the driver can use the fonts during the rendering.
  385. *
  386. * @return void
  387. */
  388. protected function registerFonts()
  389. {
  390. foreach ( $this->styles->getDefinitions( 'font-face' ) as $font )
  391. {
  392. if ( !isset( $font->formats['font-family'] ) )
  393. {
  394. $this->triggerError( E_WARNING, "Missing font-family declaration in @font-face specification.", $font->file, $font->line );
  395. continue;
  396. }
  397. $name = $font->formats['font-family']->value;
  398. if ( !isset( $font->formats['src'] ) )
  399. {
  400. $this->triggerError( E_WARNING, "Missing src declaration in @font-face specification.", $font->file, $font->line );
  401. continue;
  402. }
  403. $pathes = $font->formats['src']->value;
  404. $style = ezcDocumentPdfDriver::FONT_PLAIN;
  405. if ( isset( $font->formats['font-style'] ) &&
  406. ( ( $font->formats['font-style']->value === 'oblique' ) ||
  407. ( $font->formats['font-style']->value === 'italic' ) ) )
  408. {
  409. $style |= ezcDocumentPdfDriver::FONT_OBLIQUE;
  410. }
  411. if ( isset( $font->formats['font-weight'] ) &&
  412. ( ( $font->formats['font-weight']->value === 'bold' ) ||
  413. ( $font->formats['font-weight']->value === 'bolder' ) ) )
  414. {
  415. $style |= ezcDocumentPdfDriver::FONT_BOLD;
  416. }
  417. $this->driver->registerFont( $name, $style, $pathes );
  418. }
  419. }
  420. /**
  421. * Check column or page skip prerequisite
  422. *
  423. * If no content has been rendered any more in the current column, this
  424. * method should be called to check prerequisite for the skip, which is
  425. * especially important for already rendered items, which impose
  426. * assumptions on following contents.
  427. *
  428. * One example for this are titles, which should always be followed by at
  429. * least some content in the same column.
  430. *
  431. * Returns false, if prerequisite are not fulfileld and rendering should be
  432. * aborted.
  433. *
  434. * @param float $move
  435. * @param float $width
  436. * @return bool
  437. */
  438. public function checkSkipPrerequisites( $move, $width )
  439. {
  440. // Ensure the paragraph is on the same page / in the same column
  441. // like a title, of it is the first paragraph
  442. if ( $this->titleTransaction === null )
  443. {
  444. return true;
  445. }
  446. $this->driver->revert( $this->titleTransaction['transaction'] );
  447. // The rendering should now start again with the title on the
  448. // next column / page.
  449. $this->getNextRenderingPosition( $move, $width );
  450. $this->restart = $this->titleTransaction['position'] - 1;
  451. $this->titleTransaction = null;
  452. return false;
  453. }
  454. /**
  455. * Calculate text width
  456. *
  457. * Calculate the available horizontal space for texts depending on the
  458. * page layout settings.
  459. *
  460. * @param ezcDocumentPdfPage $page
  461. * @param ezcDocumentLocateableDomElement $text
  462. * @return float
  463. */
  464. public function calculateTextWidth( ezcDocumentPdfPage $page, ezcDocumentLocateableDomElement $text )
  465. {
  466. // Inference page styles
  467. $rules = $this->styles->inferenceFormattingRules( $text );
  468. return ( $page->innerWidth -
  469. ( $rules['text-column-spacing']->value * ( $rules['text-columns']->value - 1 ) )
  470. ) / $rules['text-columns']->value
  471. - $page->xOffset - $page->xReduce;
  472. }
  473. /**
  474. * Get next rendering position
  475. *
  476. * If the current space has been exceeded this method calculates
  477. * a new rendering position, optionally creates a new page for
  478. * this, or switches to the next column. The new rendering
  479. * position is set on the returned page object.
  480. *
  481. * As the parameter you need to pass the required width for the object to
  482. * place on the page.
  483. *
  484. * @param float $move
  485. * @param float $width
  486. * @return ezcDocumentPdfPage
  487. */
  488. public function getNextRenderingPosition( $move, $width )
  489. {
  490. // Then move paragraph into next column / page;
  491. $trans = $this->driver->startTransaction();
  492. $page = $this->driver->currentPage();
  493. if ( ( ( $newX = $page->x + $move ) < ( $page->startX + $page->innerWidth ) ) &&
  494. ( ( $space = $page->testFitRectangle( $newX, null, $width, 2 ) ) !== false ) )
  495. {
  496. // Another column fits on the current page, find starting Y
  497. // position
  498. $page->x = $space->x;
  499. $page->y = $space->y;
  500. return $page;
  501. }
  502. // If there is no space for a new column, create a new page
  503. $oldPage = $page;
  504. $page = $this->driver->appendPage( $this->styles );
  505. $page->xOffset = $oldPage->xOffset;
  506. $page->xReduce = $oldPage->xReduce;
  507. foreach ( $this->parts as $part )
  508. {
  509. $part->hookPageCreation( $page );
  510. }
  511. return $page;
  512. }
  513. /**
  514. * Process a single element with the registered renderers.
  515. *
  516. * @param DOMElement $element
  517. * @param int $number
  518. * @return int
  519. */
  520. public function processNode( DOMElement $element, $number = 0 )
  521. {
  522. // Default to docbook namespace, if no namespace is defined
  523. $namespace = $element->namespaceURI === null ? 'http://docbook.org/ns/docbook' : $element->namespaceURI;
  524. if ( !isset( $this->handlerMapping[$namespace] ) ||
  525. !isset( $this->handlerMapping[$namespace][$element->tagName] ) )
  526. {
  527. $this->triggerError(
  528. E_NOTICE,
  529. "Unknown and unhandled element: {$namespace}:{$element->tagName}."
  530. );
  531. return $number;
  532. }
  533. $method = $this->handlerMapping[$namespace][$element->tagName];
  534. $this->$method( $element, $number );
  535. // Check if the rendering process should be restarted at an earlier
  536. // point
  537. if ( $this->restart !== false )
  538. {
  539. $number = $this->restart;
  540. $this->restart = false;
  541. return $number;
  542. }
  543. return $number;
  544. }
  545. /**
  546. * Recurse into DOMDocument tree and call appropriate element handlers
  547. *
  548. * @param DOMNode $element
  549. * @return void
  550. */
  551. public function process( DOMNode $element )
  552. {
  553. $childNodes = $element->childNodes;
  554. $nodeCount = $childNodes->length;
  555. for ( $i = 0; $i < $nodeCount; ++$i )
  556. {
  557. $child = $childNodes->item( $i );
  558. if ( $child->nodeType !== XML_ELEMENT_NODE )
  559. {
  560. continue;
  561. }
  562. $i = $this->processNode( $child, $i );
  563. }
  564. }
  565. /**
  566. * Ignore elements, which should not be rendered
  567. *
  568. * @param ezcDocumentLocateableDomElement $element
  569. * @return void
  570. */
  571. private function ignore( ezcDocumentLocateableDomElement $element )
  572. {
  573. // Just do nothing.
  574. }
  575. /**
  576. * Initialize document according to detected root node
  577. *
  578. * @param ezcDocumentLocateableDomElement $element
  579. * @return void
  580. */
  581. private function initializeDocument( ezcDocumentLocateableDomElement $element )
  582. {
  583. // Call hooks for started document
  584. foreach ( $this->parts as $part )
  585. {
  586. $part->hookDocumentCreation( $element );
  587. }
  588. $page = $this->driver->appendPage( $this->styles );
  589. // Call hooks for fresh new first page
  590. foreach ( $this->parts as $part )
  591. {
  592. $part->hookPageCreation( $page );
  593. }
  594. // Continue processing sub nodes
  595. $this->process( $element );
  596. // Call hooks for finished document
  597. foreach ( $this->parts as $part )
  598. {
  599. $part->hookDocumentRendering( $element );
  600. }
  601. }
  602. /**
  603. * Append document metadata
  604. *
  605. * @param ezcDocumentLocateableDomElement $element
  606. * @return void
  607. */
  608. private function appendMetaData( ezcDocumentLocateableDomElement $element )
  609. {
  610. $childNodes = $element->childNodes;
  611. $nodeCount = $childNodes->length;
  612. // Default metadata values
  613. $metadata = array(
  614. 'created' => date( 'r' ),
  615. 'modified' => date( 'r' ),
  616. );
  617. // Fields mapped to metadata identifiers
  618. $fields = array(
  619. 'http://docbook.org/ns/docbook' => array(
  620. 'title' => 'title',
  621. 'author' => 'author',
  622. 'authors' => 'author',
  623. 'subtitle' => 'subject',
  624. 'pubdate' => 'created',
  625. 'date' => 'modified',
  626. ),
  627. );
  628. for ( $i = 0; $i < $nodeCount; ++$i )
  629. {
  630. $child = $childNodes->item( $i );
  631. if ( $child->nodeType !== XML_ELEMENT_NODE )
  632. {
  633. continue;
  634. }
  635. $namespace = $element->namespaceURI === null ? 'http://docbook.org/ns/docbook' : $element->namespaceURI;
  636. if ( isset( $fields[$namespace] ) &&
  637. isset( $fields[$namespace][$child->tagName] ) )
  638. {
  639. $metadata[$fields[$namespace][$child->tagName]] = $child->textContent;
  640. }
  641. }
  642. foreach ( $metadata as $key => $value )
  643. {
  644. $this->driver->setMetaData( $key, $value );
  645. }
  646. }
  647. /**
  648. * Handle calls to block element renderer
  649. *
  650. * @param ezcDocumentLocateableDomElement $element
  651. * @return void
  652. */
  653. private function renderBlock( ezcDocumentLocateableDomElement $element )
  654. {
  655. $renderer = new ezcDocumentPdfBlockRenderer( $this->driver, $this->styles );
  656. $page = $this->driver->currentPage();
  657. return $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  658. }
  659. /**
  660. * Handle calls to block element renderer
  661. *
  662. * @param ezcDocumentLocateableDomElement $element
  663. * @return void
  664. */
  665. private function renderBlockquote( ezcDocumentLocateableDomElement $element )
  666. {
  667. $renderer = new ezcDocumentPdfBlockquoteRenderer( $this->driver, $this->styles );
  668. $page = $this->driver->currentPage();
  669. return $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  670. }
  671. /**
  672. * Handle calls to table element renderer
  673. *
  674. * @param ezcDocumentLocateableDomElement $element
  675. * @return void
  676. */
  677. private function renderTable( ezcDocumentLocateableDomElement $element )
  678. {
  679. $renderer = new ezcDocumentPdfTableRenderer( $this->driver, $this->styles, $this->options );
  680. $page = $this->driver->currentPage();
  681. return $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  682. }
  683. /**
  684. * Handle calls to List element renderer
  685. *
  686. * @param ezcDocumentLocateableDomElement $element
  687. * @return void
  688. */
  689. private function renderList( ezcDocumentLocateableDomElement $element )
  690. {
  691. $renderer = new ezcDocumentPdfListRenderer( $this->driver, $this->styles );
  692. $page = $this->driver->currentPage();
  693. return $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  694. }
  695. /**
  696. * Handle calls to list item element renderer
  697. *
  698. * @param ezcDocumentLocateableDomElement $element
  699. * @return void
  700. */
  701. private function renderListItem( ezcDocumentLocateableDomElement $element )
  702. {
  703. $renderer = new ezcDocumentPdfListItemRenderer( $this->driver, $this->styles, new ezcDocumentNoListItemGenerator(), 0 );
  704. $page = $this->driver->currentPage();
  705. return $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  706. }
  707. /**
  708. * Handle calls to paragraph renderer
  709. *
  710. * @param ezcDocumentLocateableDomElement $element
  711. * @return void
  712. */
  713. private function renderParagraph( ezcDocumentLocateableDomElement $element )
  714. {
  715. $renderer = new ezcDocumentPdfWrappingTextBoxRenderer( $this->driver, $this->styles );
  716. $page = $this->driver->currentPage();
  717. $styles = $this->styles->inferenceFormattingRules( $element );
  718. // Just try to render at current position first
  719. $trans = $this->driver->startTransaction();
  720. if ( $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this ) )
  721. {
  722. $this->titleTransaction = null;
  723. $this->handleAnchors( $element );
  724. return true;
  725. }
  726. // Check if something requested a rendering restart at a prior point,
  727. // only continue otherwise.
  728. if ( ( $this->restart !== false ) ||
  729. ( !$this->checkSkipPrerequisites(
  730. ( $pWidth = $this->calculateTextWidth( $page, $element ) ) +
  731. $styles['text-column-spacing']->value,
  732. $pWidth
  733. ) ) )
  734. {
  735. return false;
  736. }
  737. // If that did not work, switch to the next possible location and start
  738. // there.
  739. $this->driver->revert( $trans );
  740. $this->getNextRenderingPosition(
  741. ( $pWidth = $this->calculateTextWidth( $page, $element ) ) +
  742. $styles['text-column-spacing']->value,
  743. $pWidth
  744. );
  745. return $this->renderParagraph( $element );
  746. }
  747. /**
  748. * Handle calls to title renderer
  749. *
  750. * @param ezcDocumentLocateableDomElement $element
  751. * @param int $position
  752. */
  753. private function renderTitle( ezcDocumentLocateableDomElement $element, $position )
  754. {
  755. $styles = $this->styles->inferenceFormattingRules( $element );
  756. $renderer = new ezcDocumentPdfTitleRenderer( $this->driver, $this->styles );
  757. $page = $this->driver->currentPage();
  758. // Just try to render at current position first
  759. $this->titleTransaction = array(
  760. 'transaction' => $this->driver->startTransaction(),
  761. 'page' => $page,
  762. 'xPos' => $page->x,
  763. 'position' => $position,
  764. );
  765. if ( $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this ) )
  766. {
  767. $this->handleAnchors( $element );
  768. return true;
  769. }
  770. $this->driver->revert( $this->titleTransaction['transaction'] );
  771. $this->getNextRenderingPosition(
  772. ( $pWidth = $this->calculateTextWidth( $page, $element ) ) +
  773. $styles['text-column-spacing']->value,
  774. $pWidth
  775. );
  776. return $this->renderTitle( $element, $position );
  777. }
  778. /**
  779. * Handle calls to media object renderer
  780. *
  781. * @param ezcDocumentLocateableDomElement $element
  782. * @return void
  783. */
  784. private function renderMediaObject( ezcDocumentLocateableDomElement $element )
  785. {
  786. $renderer = new ezcDocumentPdfMediaObjectRenderer( $this->driver, $this->styles );
  787. $page = $this->driver->currentPage();
  788. // Just try to render at current position first
  789. $trans = $this->driver->startTransaction();
  790. $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this );
  791. $this->handleAnchors( $element );
  792. }
  793. /**
  794. * Handle calls to paragraph renderer
  795. *
  796. * @param ezcDocumentLocateableDomElement $element
  797. * @return void
  798. */
  799. private function renderLiteralLayout( ezcDocumentLocateableDomElement $element )
  800. {
  801. $renderer = new ezcDocumentPdfLiteralBlockRenderer( $this->driver, $this->styles );
  802. $page = $this->driver->currentPage();
  803. $styles = $this->styles->inferenceFormattingRules( $element );
  804. // Just try to render at current position first
  805. $trans = $this->driver->startTransaction();
  806. if ( $renderer->renderNode( $page, $this->hyphenator, $this->tokenizer, $element, $this ) )
  807. {
  808. $this->titleTransaction = null;
  809. $this->handleAnchors( $element );
  810. return true;
  811. }
  812. // Check if something requested a rendering restart at a prior point,
  813. // only continue otherwise.
  814. if ( ( $this->restart !== false ) ||
  815. ( !$this->checkSkipPrerequisites(
  816. ( $pWidth = $this->calculateTextWidth( $page, $element ) ) +
  817. $styles['text-column-spacing']->value,
  818. $pWidth
  819. ) ) )
  820. {
  821. return false;
  822. }
  823. // If that did not work, switch to the next possible location and start
  824. // there.
  825. $this->driver->revert( $trans );
  826. $this->getNextRenderingPosition(
  827. ( $pWidth = $this->calculateTextWidth( $page, $element ) ) +
  828. $styles['text-column-spacing']->value,
  829. $pWidth
  830. );
  831. return $this->renderParagraph( $element );
  832. }
  833. /**
  834. * Handle all anchors inside the current element
  835. *
  836. * Finds all anchors somewhere in the current element and adds reference
  837. * targets for them.
  838. *
  839. * @param ezcDocumentLocateableDomElement $element
  840. * @return void
  841. */
  842. private function handleAnchors( ezcDocumentLocateableDomElement $element )
  843. {
  844. $xpath = new DOMXPath( $element->ownerDocument );
  845. $xpath->registerNamespace( 'doc', 'http://docbook.org/ns/docbook' );
  846. foreach ( $xpath->query( './/doc:anchor', $element ) as $anchor )
  847. {
  848. $this->driver->addInternalLinkTarget( $anchor->getAttribute( 'id' ) );
  849. }
  850. }
  851. }
  852. ?>