/** * Render a single text box * * All markup inside of the given string is considered inline markup (in * CSS terms). Inline markup should be given as common docbook inline * markup, like <emphasis>. * * Returns a boolean indicator whether the rendering of the full text * in the available space succeeded or not. * * @param ezcDocumentPdfPage $page * @param ezcDocumentPdfHyphenator $hyphenator * @param ezcDocumentPdfTokenizer $tokenizer * @param ezcDocumentLocateableDomElement $text * @param ezcDocumentPdfMainRenderer $mainRenderer * @return bool * * @todo This method does not respect changes in the available text width, * if a paragraph is wrapped to the next page. This would require token * reordering, which is not implemented yet. */ public function renderNode(ezcDocumentPdfPage $page, ezcDocumentPdfHyphenator $hyphenator, ezcDocumentPdfTokenizer $tokenizer, ezcDocumentLocateableDomElement $text, ezcDocumentPdfMainRenderer $mainRenderer) { // Inference page styles $styles = $this->styles->inferenceFormattingRules($text); $width = $mainRenderer->calculateTextWidth($page, $text); // Evaluate available space if (($space = $this->evaluateAvailableBoundingBox($page, $styles, $width)) === false) { return false; } // Iterate over tokens and try to fit them in the current line, use // hyphenator to split words. $tokens = $this->tokenize($text, $tokenizer); $lines = $this->fitTokensInLines($tokens, $hyphenator, $space->width); // Transaction wrapping around temporary page creations $transaction = $this->driver->startTransaction(); $lineCount = count($lines); $current = 0; $position = $space->y; $pageNr = 0; $wrap = false; $pages = array($pageNr => array('page' => $page, 'lines' => array(), 'space' => $space)); for ($line = 0; $line < $lineCount; ++$line) { // Render on current page, of there is still enough space if (!$wrap && $position + $lines[$line]['height'] < $pages[$pageNr]['space']->y + $pages[$pageNr]['space']->height) { ++$current; // Check widows, if we are at the last line if ($line === $lineCount - 1 && $current < $styles['widows']->value && $lineCount >= $styles['widows']->value) { $difference = $styles['widows']->value - $current; $pages[$pageNr - 1]['lines'] = array_slice($pages[$pageNr - 1]['lines'], 0, -$difference, true); $pages[$pageNr]['lines'] = array(); $line = $lineCount - $styles['widows']->value - 1; $current = 0; continue; } $pages[$pageNr]['lines'][] = array('position' => $position, 'line' => $lines[$line]); $position += $lines[$line]['height'] * $styles['line-height']->value; continue; } // Shift to next page $pages[++$pageNr] = array('page' => $tmpPage = $mainRenderer->getNextRenderingPosition(($pWidth = $mainRenderer->calculateTextWidth($page, $text)) + $styles['text-column-spacing']->value, $pWidth), 'lines' => array(), 'space' => $this->evaluateAvailableBoundingBox($tmpPage, $styles, $width)); $position = $pages[$pageNr]['space']->y; $current = 0; $wrap = false; // Handle orphans if ($line < $styles['orphans']->value && $line < $lineCount) { $pages[0]['lines'] = array(); $line = -1; continue; } --$line; } $this->driver->revert($transaction); // Render lines $lineNr = 0; foreach ($pages as $nr => $content) { if ($nr > 0) { // Get next rendering position $page = $mainRenderer->getNextRenderingPosition(($pWidth = $mainRenderer->calculateTextWidth($page, $text)) + $styles['text-column-spacing']->value, $pWidth); } if (!count($content['lines'])) { continue; } // Render background & border $space = $content['space']; $lastLine = end($content['lines']); $space->height = $lastLine['position'] + $lastLine['line']['height'] - $space->y; $this->renderBoxBackground($space, $styles); $this->renderBoxBorder($space, $styles, $nr === 0, $nr + 1 >= count($pages)); $this->setBoxCovered($page, $space, $styles); // Render actual text contents foreach ($content['lines'] as $line) { $this->renderLine($line['position'], $lineNr++, $line['line'], $space, $styles); } } return true; }
/** * Process to render the table into its boundings. * * @param ezcDocumentPdfPage $page * @param ezcDocumentPdfHyphenator $hyphenator * @param ezcDocumentPdfTokenizer $tokenizer * @param ezcDocumentLocateableDomElement $block * @param ezcDocumentPdfMainRenderer $mainRenderer */ protected function processTable(ezcDocumentPdfPage $page, ezcDocumentPdfHyphenator $hyphenator, ezcDocumentPdfTokenizer $tokenizer, ezcDocumentLocateableDomElement $block, ezcDocumentPdfMainRenderer $mainRenderer) { $tableColumnWidths = $this->options->tableColumnWidthCalculator->estimateWidths($block); $styles = $this->styles->inferenceFormattingRules($block); $renderWidth = $mainRenderer->calculateTextWidth($page, $block); $space = $this->evaluateAvailableBoundingBox($page, $styles, $renderWidth); $this->tableBox = array('box' => $box = clone $space, 'styles' => $styles); $this->renderTopBorder($styles, $box); $xpath = new DOMXPath($block->ownerDocument); $xPosition = $page->x; foreach ($xpath->query('./*/*/*[local-name() = "row"] | ./*/*[local-name() = "row"]', $block) as $rowNr => $row) { $xOffset = 0; $this->cellBoxes = array(); $positions = array(); $pageStartId = $page->orderedId; $lastPage = array(); foreach ($xpath->query('./*[local-name() = "entry"]', $row) as $nr => $cell) { list($pageId, $position) = $this->renderCell($page, $hyphenator, $tokenizer, $cell, $styles, $space, $xOffset, $tableColumnWidths[$nr]); $positions[$pageId][] = $position; $xOffset += $tableColumnWidths[$nr]; $lastPage[$pageId] = $this->lastPageForCell; // Go back to page, where each cell in the row shoudl start the // rendering $this->driver->selectPage($pageStartId); } // Close borders foreach ($this->additionalBorders as $page => $borders) { $this->driver->selectPage($page); foreach ($borders as $border) { call_user_func_array(array($this, 'renderBoxBorder'), $border); } } $this->additionalBorders = array(); $lastPageId = max(array_keys($positions)); $page = $lastPage[$lastPageId]; // Forward page to last page used during cell rendering $this->driver->selectPage($lastPageId); $page->x = $xPosition; foreach ($this->cellBoxes as $cell) { $cell['box']->height = max($positions[$lastPageId]) - $cell['box']->y; $this->renderBoxBorder($cell['box'], $cell['styles'], false); $this->setCellCovered($page, $cell['box'], $styles); } // Set page->y again, since setBoxCovered() increased it, which we // do not want in this case. $page->y = max($positions[$lastPageId]); $space->y = $page->y = $page->y + $cell['styles']['padding']->value['bottom'] + $cell['styles']['border']->value['bottom']['width'] + $cell['styles']['margin']->value['bottom']; } $box->height = $space->y - $box->y; $this->renderBoxBorder($box, $styles, false); }