/** * Render a media object. * * @param ezcDocumentPdfPage $page * @param ezcDocumentPdfHyphenator $hyphenator * @param ezcDocumentPdfTokenizer $tokenizer * @param ezcDocumentLocateableDomElement $media * @param ezcDocumentPdfMainRenderer $mainRenderer * @return bool */ public function renderNode(ezcDocumentPdfPage $page, ezcDocumentPdfHyphenator $hyphenator, ezcDocumentPdfTokenizer $tokenizer, ezcDocumentLocateableDomElement $media, ezcDocumentPdfMainRenderer $mainRenderer) { // Inference page styles $styles = $this->styles->inferenceFormattingRules($media); // Locate image file $imageData = $media->getElementsByTagName('imagedata')->item(0); $imageFile = $mainRenderer->locateFile((string) $imageData->getAttribute('fileref')); $image = ezcDocumentPdfImage::createFromFile($imageFile); // Estimate size of image box $width = $this->getMediaBoxWidth($styles, $page, $image); // Get available height with the estimated width $dimensions = $this->scaleImage($styles, $image, $page, $width); $switchBack = false; if (($space = $page->testFitRectangle($page->x, $page->y, $dimensions[0], $dimensions[1])) === false) { // Image with estimated dimensions does not fit on current page any // more. $page = $mainRenderer->getNextRenderingPosition(($pWidth = $width->get()) + $styles['text-column-spacing']->value, $pWidth); $switchBack = true; } // Get maximum available space $space = $page->testFitRectangle($page->x, $page->y, $width->get(), null); // Apply margin of mediaobject $space->x += $styles['margin']->value['left']; $space->y += $styles['margin']->value['top']; $space->width -= $styles['margin']->value['left'] + $styles['margin']->value['right']; $space->height -= $styles['margin']->value['top'] + $styles['margin']->value['bottom']; // Estimate required height of text blocks $captions = $media->getElementsByTagName('caption'); $captionHeight = 0; $textRenderer = new ezcDocumentPdfTextBlockRenderer($this->driver, $this->styles); foreach ($captions as $caption) { $captionHeight += $textRenderer->estimateHeight($space->width, $hyphenator, $tokenizer, $caption); } if (($imageHeight = $space->height - $captionHeight) < 0) { return false; } // Reduce the image size, of it does not fit any more because of the captions if ($imageHeight < $dimensions[1]) { $dimensions[0] *= $imageHeight / $dimensions[1]; $dimensions[1] = $imageHeight; } // Render image $this->driver->drawImage($imageFile, $image->getMimeType(), $space->x + ($space->width - $dimensions[0]) / 2, $space->y, $dimensions[0], $dimensions[1]); $space->y += $dimensions[1]; // Render captions foreach ($captions as $caption) { $space->y += $textRenderer->renderBlock($space, $hyphenator, $tokenizer, $caption); } // Set covered space covered $page->setCovered(new ezcDocumentPdfBoundingBox($page->x, $page->y, $space->width, $space->y - $page->y)); $page->y = $space->y; // Go back to previous page, if requested if ($switchBack) { $this->driver->goBackOnePage(); } return true; }
/** * 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; }
/** * Get next rendering position. * * If the current space has been exceeded this method calculates * a new rendering position, optionally creates a new page for * this, or switches to the next column. The new rendering; * position is set on the returned page object. * * As the parameter you need to pass the required width for the object to * place on the page. * * @param float $move * @param float $width * @return ezcDocumentPdfPage */ public function getNextRenderingPosition($move, $width) { // Close all table cells $oldPage = $this->driver->currentPage(); foreach ($this->cellBoxes as $nr => $cell) { if (isset($this->additionalBorders[$oldPage->orderedId]) && isset($this->additionalBorders[$oldPage->orderedId][$nr])) { continue; } $cell['box']->height = $oldPage->startY + $oldPage->innerHeight - $cell['box']->y; $this->additionalBorders[$oldPage->orderedId][$nr] = array(clone $cell['box'], $cell['styles'], false); } // Close table border if (!isset($this->additionalBorders[$oldPage->orderedId]) || !isset($this->additionalBorders[$oldPage->orderedId]['table'])) { $this->tableBox['box']->height = $oldPage->startY + $oldPage->innerHeight - $this->tableBox['box']->y; $this->additionalBorders[$oldPage->orderedId]['table'] = array(clone $this->tableBox['box'], $this->tableBox['styles'], false, false); } // Call parent handler to get next rendering position $page = parent::getNextRenderingPosition($move, $width); if ($page === $oldPage) { $this->lastPageForCell = $page; return $page; } // Update all boxes to start on top of the new page foreach ($this->cellBoxes as $cell) { $cell['box']->y = $page->y; $this->renderTopBorder($cell['styles'], $cell['box']); } // Maintain horizontal rendering position $page->x = $oldPage->x; $page->y = $page->startY; $this->tableBox['box']->y = $page->y; return $this->lastPageForCell = $page; }