【问题标题】:How to calculate the height of a MultiCell/writeHTMLCell in TCPDF?如何计算 TCPDF 中 MultiCell/writeHTMLCell 的高度?
【发布时间】:2010-11-07 21:38:46
【问题描述】:

我尝试创建一个包含多个页面的 PDF,并且需要提前计算每个单独元素 (MultiCell) 的高度以准备分页符。根据文档,有几个函数,如 GetCharWidth/GetStringWidth 来支持我自己做,但除了潜在的性能损失之外,我可能无论如何都不会做正确的事。以更优雅的方式实现我的目标的建议?

参考:TCPDF

【问题讨论】:

    标签: php pdf tcpdf


    【解决方案1】:

    我明白了: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

    【讨论】:

    • 起初我对这个答案有点怀疑,但它确实工作得很好。实际上比我迄今为止尝试过的任何其他方法都要好。虽然你不应该忘记将 $pdf2 放在与 $pdf1 相同的字体上
    • 公平地说,实际上效果很好。如果你正在克隆,那么字体应该是相同的。如果您使用它来动态生成表格,请不要忘记宽度也很重要
    • 如果 $ln=0,这对 MultiCell 不起作用,因为光标向右移动并且 getY() 不会给出“最高”单元格的底部(如果文本太长而无法容纳在其中 - 因此单元格高度不同)。
    • 因此您可能必须使用 $ln=1 并在每个 MultiCell 之后重置 y 和 X。
    • 我正在使用 writeHTMLCell 打印一个复杂的表格,但我不想拆分表格。在尝试了所有我能找到和想到的不同解决方案之后,我又回到了这个。这种双重打印似乎是唯一准确且一致的解决方案。为了让事情变得更简单,我创建了一个函数来打印整个表格,并在第一次调用时传入克隆($pdf2),然后在中断后传入原始 pfd 对象。我所做的唯一更改是克隆的 getY 包含标题。所以我必须减去它才能得到我正在打印的桌子的真实高度。
    【解决方案2】:

    这是一个老问题,但当前版本的 TCPDF(截至 2011 年 12 月 7 日)有一个名为 getStringHeight 的函数,允许您在实际调用 MultiCell 之前计算传递给 MultiCell 的字符串的结果高度。然后这个高度可以用于各种事情,原始问题中的计算,以及在制作表格时设置行高等。效果很好。

    只是一些信息,以防其他人像我一样偶然发现这个问题并寻找解决方案。

    【讨论】:

    • "...也用于在制作表格时设置行高" ==> YES。我有点困惑地发现 Multicells 不会自动将行高设置为最高单元格的高度。但是使用通过这种方法获得的行中文本的最大高度是一个很容易解决的问题。
    • 链接不再有效,这里是正确的:tcpdf.org/docs/srcdoc/tcpdf/class-TCPDF/#_getStringHeight
    【解决方案3】:

    虽然 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();
    

    【讨论】:

    • 如果$ln=0,这是一个问题,因为那时$start_y = $end_y。我希望他们在向右移动光标之前存储 Y 的值。
    • 因此您可能必须使用 $ln=1 并在每个 MultiCell 之后重置 y 和 X。
    • 注意!在最新版本中,某些属性受到保护,您必须更改以下内容:$pdf-&gt;h$pdf-&gt;getPageHeight()$pdf-&gt;bMargin$pdf-&gt;getBreakMargin()$pdf-&gt;tMargin$pdf-&gt;getMargins()['top']
    【解决方案4】:

    根据我的经验,几乎不可能提前计算出像元高度。使用 TCPDF 的分页符处理功能要容易得多,它可以提前告诉您是否要进入分页符。这是示例代码:

    $yy = $this->pdf->GetY();
    
    $check_pagebreak = $this->pdf->checkPageBreak($height+$padding,$yy,false);
    

    将 false 更改为 true 以允许自动分页,否则,您可以自己处理分页的逻辑,这就是我最终要做的。

    另外,如果您可能需要这个,这里还有一个小提示:考虑使用事务功能分两次创建您的文档。第一遍用于计算所有高度和单元格、分页符等。您还可以将所有行高和每页的行数存储在数组中。在第二遍中,使用所有正确的信息创建您的文档,并且不需要分页逻辑(第二遍可以从单独的方法运行,以使代码更易于阅读,并且为了您的理智)。

    【讨论】:

      【解决方案5】:

      使用 TCPDF 示例 20

      1. 如果单元格/列在不同的页面上结束,计算多单元格高度可能是一场噩梦。

      2. 使用事务或额外的 pdf 对象会使事情变得非常缓慢。

      3. 在打印单元格之前使用 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);
      }
      

      【讨论】:

        【解决方案6】:

        Revisited: Tcpdf – Variable Height Table Rows With MultiCell 的帖子有很多有用的信息。这是一个简短的摘录:

        getNumLines() ...实际上允许我们确定给定特定宽度的文本字符串将占用多少行。实际上,它允许我们执行我使用 MultiCell 返回的操作,而无需实际绘制任何内容。这让我们可以用一行代码确定最大单元格高度:

        $linecount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80
        

        【讨论】:

        • getNumLines() 和 getStringHeight() 都只给出“估计高度”(参见文档)。如果文本正好在单元格右边框之前或之后结束,这些函数并不总是正确工作 - 导致行被打印在彼此的顶部。而是使用示例 20 中的技术。
        【解决方案7】:

        我尝试使用 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() 包装器。

        【讨论】:

          猜你喜欢
          • 2012-03-29
          • 2014-05-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-28
          • 2012-01-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多