【发布时间】:2010-11-07 21:38:46
【问题描述】:
我尝试创建一个包含多个页面的 PDF,并且需要提前计算每个单独元素 (MultiCell) 的高度以准备分页符。根据文档,有几个函数,如 GetCharWidth/GetStringWidth 来支持我自己做,但除了潜在的性能损失之外,我可能无论如何都不会做正确的事。以更优雅的方式实现我的目标的建议?
参考:TCPDF
【问题讨论】:
我尝试创建一个包含多个页面的 PDF,并且需要提前计算每个单独元素 (MultiCell) 的高度以准备分页符。根据文档,有几个函数,如 GetCharWidth/GetStringWidth 来支持我自己做,但除了潜在的性能损失之外,我可能无论如何都不会做正确的事。以更优雅的方式实现我的目标的建议?
参考:TCPDF
【问题讨论】:
我明白了:D!!!!!!
创建另一个 pdf2 对象
// pdf2 set x margin to pdf1's xmargin, but y margin to zero
// to make sure that pdf2 has identical settings, you can clone the object (after initializing the main pdf object)
$pdf2 = clone $pdf;
pdf2->addpage
pdf2->writeCell
$height = pdf2->getY()
pdf2->deletePage(pdf2->getPage())
pdf1->checkPageBreak($height);
pdf1->writeCell()
W00tness :D
【讨论】:
这是一个老问题,但当前版本的 TCPDF(截至 2011 年 12 月 7 日)有一个名为 getStringHeight 的函数,允许您在实际调用 MultiCell 之前计算传递给 MultiCell 的字符串的结果高度。然后这个高度可以用于各种事情,原始问题中的计算,以及在制作表格时设置行高等。效果很好。
只是一些信息,以防其他人像我一样偶然发现这个问题并寻找解决方案。
【讨论】:
虽然 Carvell 的回答很棒,但 TCPDF 提到 getStringHeight 返回估计的高度。有用的是,那里的文档提供了一种非常全面的技术来获得准确的高度,即$height。至于为什么他们自己不使用这个是个谜……
// store current object
$pdf->startTransaction();
// store starting values
$start_y = $pdf->GetY();
$start_page = $pdf->getPage();
// call your printing functions with your parameters
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// get the new Y
$end_y = $pdf->GetY();
$end_page = $pdf->getPage();
// calculate height
$height = 0;
if ($end_page == $start_page) {
$height = $end_y - $start_y;
} else {
for ($page=$start_page; $page <= $end_page; ++$page) {
$pdf->setPage($page);
if ($page == $start_page) {
// first page
$height = $pdf->h - $start_y - $pdf->bMargin;
} elseif ($page == $end_page) {
// last page
$height = $end_y - $pdf->tMargin;
} else {
$height = $pdf->h - $pdf->tMargin - $pdf->bMargin;
}
}
}
// restore previous object
$pdf = $pdf->rollbackTransaction();
【讨论】:
$pdf->h 到 $pdf->getPageHeight()、$pdf->bMargin 到 $pdf->getBreakMargin() 和 $pdf->tMargin 到 $pdf->getMargins()['top']
根据我的经验,几乎不可能提前计算出像元高度。使用 TCPDF 的分页符处理功能要容易得多,它可以提前告诉您是否要进入分页符。这是示例代码:
$yy = $this->pdf->GetY();
$check_pagebreak = $this->pdf->checkPageBreak($height+$padding,$yy,false);
将 false 更改为 true 以允许自动分页,否则,您可以自己处理分页的逻辑,这就是我最终要做的。
另外,如果您可能需要这个,这里还有一个小提示:考虑使用事务功能分两次创建您的文档。第一遍用于计算所有高度和单元格、分页符等。您还可以将所有行高和每页的行数存储在数组中。在第二遍中,使用所有正确的信息创建您的文档,并且不需要分页逻辑(第二遍可以从单独的方法运行,以使代码更易于阅读,并且为了您的理智)。
【讨论】:
使用 TCPDF 示例 20
如果单元格/列在不同的页面上结束,计算多单元格高度可能是一场噩梦。
使用事务或额外的 pdf 对象会使事情变得非常缓慢。
在打印单元格之前使用 getNumLines() 和 getStringHeight() 等函数计算“估计”(参见文档)高度并不总是正确的。特别是如果文本在单元格右边框之前或之后结束 - 导致行被打印在彼此的顶部。
我更喜欢Example 20 中使用的技术,其中不同页面的最大 Y 值用于计算新行的位置。
该示例仅打印两列,但我更改了它的 main 函数以能够打印列数组。显然你可以在数组中添加更多的数据,比如每列的字体、边框等。
public function MultiRow($columnsArray) {
$page_start = $this->getPage();
$y_start = $this->GetY();
$pageArray = array();
$yArray = array();
// traverse through array and print one column at a time.
$columnCount = count($columnsArray);
for($i=0; $i<$columnCount; $i++)
{
if($i+1 < $columnCount)
{
// Current column is not the last column in the row.
// After printing, the pointer will be moved down to
// the right-bottom of the column - from where the
// next multiCell in the following loop will use it
// via $this->GetX().
$ln = 2;
}
else
{
// Current column is the last column in the row.
// After printing, the pointer will be moved to new line.
$ln = 1;
}
$this->MultiCell(30, 0, $columnsArray[$i], 1, 'L', 1, $ln,
$this->GetX() ,$y_start, true, 0);
$pageArray[$i] = $this->getPage();
$yArray[$i] = $this->GetY();
// Go to page where the row started - to print the
// next column (if any).
$this->setPage($page_start);
}
// Test if all columns ended on the same page
$samePage = true;
foreach ($pageArray as $val) {
if($val != $pageArray['0'])
{
$samePage = false;
break;
}
}
// Set the new page and row position by case
if($samePage == true)
{
// All columns ended on the same page.
// Get the longest column.
$newY = max($yArray);
}
else
{
// Some columns ended on different pages.
// Get the array-keys (not the values) of all columns that
// ended on the last page.
$endPageKeys = array_keys($pageArray, max($pageArray));
// Get the Y values of all columns that ended on the last page,
// i.e. get the Y values of all columns with keys in $endPageKeys.
$yValues = array();
foreach($endPageKeys as $key)
{
$yValues[] = $yArray[$key];
}
// Get the largest Y value of all columns that ended on
// the last page.
$newY = max($yValues);
}
// Go to the last page and start at its largets Y value
$this->setPage(max($pageArray));
$this->SetXY($this->GetX(),$newY);
}
【讨论】:
Revisited: Tcpdf – Variable Height Table Rows With MultiCell 的帖子有很多有用的信息。这是一个简短的摘录:
getNumLines()...实际上允许我们确定给定特定宽度的文本字符串将占用多少行。实际上,它允许我们执行我使用 MultiCell 返回的操作,而无需实际绘制任何内容。这让我们可以用一行代码确定最大单元格高度:$linecount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80
【讨论】:
我尝试使用 Jay 的答案,它达到了预期目的,但由于某种原因导致我的徽标没有出现在第一页之后。我不想做深入分析,但与克隆有关。然后我尝试了相同的方法,但使用了事务。这产生了数百个错误。
然后我想出了这个使用相同对象的相当简单的解决方案。
/**
* Gets an accurate measurement of a cell's rendered height.
*
* @param float $width the width of the column to be rendered
* @param string $contents the contents to be rendered
*
* @return float
*/
private function getCellHeight(float $width, string $contents): float
{
$view = $this->view;
$currentPage = $view->getPage();
$currentX = $view->GetX();
$currentY = $view->GetY();
$view->AddPage();
$x = $view->GetX();
$start = $view->GetY();
$view->writeHTMLCell($width, 15, $x, $start, $contents, self::INSTANCE_BORDER, 1);
$height = $view->GetY() - $start;
$view->deletePage($view->getPage());
$view->setPage($currentPage);
$view->changePosition($currentX, $currentY);
return $height;
}
由于writeHTMLCell 函数需要$h,我使用15,但它可以是您想要的任何值,$border 值也可以。$ln 值需要设置为1,否则y 值会在GetY() 获取之前重置。changePosition 是我自己的SetXY() 包装器。
【讨论】: