【问题标题】:How to bulk generate PDF from HTML templates ready for double-sided printing in PHP?如何从准备在 PHP 中进行双面打印的 HTML 模板批量生成 PDF?
【发布时间】:2019-01-15 18:01:51
【问题描述】:

我已经为此苦苦挣扎了一段时间,感到无助。 Prestashop 使用tcpdf 从使用 Smarty 填充的 HTML 模板生成发票和交货单。我们正在努力更新发票设计,发现 tcpdf 缺乏 CSS 支持。经过一些研究,我们选择了wkhtmltopdf 作为将 HTML/CSS 模板转换为 PDF 的正确工具。

问题

商店具有将多张发票导出为单个 PDF 的功能。使用TCPDF,我能够通过在每张发票之后插入一个空白页来使批处理文件准备好进行双面打印,该发票在文件生成之前具有奇数页。但是现在我们切换到wkhtmltopdf 我无法获得相同的结果。

关键的问题是虽然wkhtmltopdf 允许使用多个HTML 模板,但似乎没有可靠的方法来determine the number of pages 在文件生成之前它们每个都将拥有。页眉和页脚模板可以接收发票最终的页数,但它们与主要内容是分开的,因此我无法相应地插入分页符。

我也尝试计算height of the content / PDF page height,但是一旦我开始导出多个模板(使用单个模板工作正常),就有various issues。这种方法也不是很好,因为将空白页面插入内容本身会导致页脚也出现在新页面上,这不是我想要的。

我的最佳尝试

我想出解决这些问题的唯一方法是非常低效的。每次将模板添加到批处理中时,我都可以使用 wkhtmltopdfwrapper 的单独实例预先生成它,获取临时文件名,使用 pdfinfo 确定它有多少页并添加空白 HTML模板相应地添加到主实例。这是一个函数的草稿,用于获取添加的最后一个模板的页数(来自扩展包装器的类,基于我在 SO 上找到的其他一些 pdfinfo questions):

/**
* Return the number of pages in the last invoice template added
* $complete === true => return the length of the entire document
*/
public function getNumPages($complete = false)
{
    if (!$complete) {
        // Generate PDF of last template added

        $tmpPdf = new WKPdf($this->_options);
        $tmpPdf->addPage($this->content, Array(
            'footer-html' => $this->footer
        ));

        /**
           The createPdf method is protected so I need to get 
           the content as string here to force the wrapper to 
           call wkhtmltopdf.
        */
        $tmpPdf->toString();
        $document = $tmpPdf->getPdfFilename();
    } else {

        // Generate entire batch
        $this->createPdf();
        $document = $this->getPdfFilename();
    }

    // Use pdfinfo to get the PDF page count
    $cmd = 'pdfinfo';
    exec("$cmd \"$document\"", $output);

    $pagecount = 0;
    foreach($output as $op)
    {
        // Extract the number
        if(preg_match("/Pages:\s*(\d+)/i", $op, $matches) === 1)
        {
            $pagecount = intval($matches[1]);
            break;
        }
    }

    return $pagecount;
}

这非常低效 - 生成一批 25 张发票大约需要 80 秒,因为我必须调用 wkhtmltopdf 25 次来创建临时 PDF 文件,以便我可以调用 pdfinfo 25 次以获取他们的个人长度并在必要时插入空白页,然后生成最终文档。

TCPDF 的优势在于它可以为您提供动态的页数,并且类似的功能需要大约 5 秒来生成包含 25 张发票的批处理文件。

有人对如何加快速度有任何想法吗?或者一个更好的主意完全这样做。我考虑过包括 dompdf 在内的各种生成工具,但 wkhtmltopdf 是最强大的。批量生成实际上仅由商店管理员在后台使用,因此也许他们可以耐心等待。但还是。

【问题讨论】:

  • 我已经尝试用 Fpdi 库替换 pdfinfo。不太喜欢使用两个工具(实际上是三个因为 Fpdi 需要 Fpdf...)来实现如此简单的事情。
  • 有趣。我会在周末看看这个。我相信我可能有一个会有所帮助的解决方案,但有一些“如果”。我相信我能以某种方式提供帮助。周末后再回来看看。
  • 我可能会使用 TCPDF 并使用背景图像方法,然后将自定义文本放在您想要的位置。如果您真的想避免使用 TCPDF(看看他们的文档,我就明白了),那么也许您可以通过为每个 PDF 制作线程来帮助您节省时间 stackoverflow.com/questions/70855/…
  • @PeterTheLobster,请给我一些应该生成的示例 PDF 文件(互联网链接)。如果我理解正确,那么在每张 A4 格式的发票之后是一个空白页吗?
  • @Bharata 嘿,对不起。我认为我目前无法为您提供示例文件,因为我没有与客户协商过。我们基本上是在尝试生成包含多张发票的单个 PDF。由于订购的物品数量或客户数据的数量,每张发票的长度可能在 1 到 3 页之间。因此,在生成包含多张发票的批量 pdf 时,我需要在每张奇数页(1、3 等)的发票之后插入一个空白页,以便可以双面打印整个文件(以避免在同一张纸)。

标签: php pdf wkhtmltopdf prestashop-1.5


【解决方案1】:

不幸的是,wkhtmltopdf 是库,它是用 C 语言编写的,我们不能像 PHP 库那样动态添加一页。

引用您的评论: 由于订购的商品数量或客户数据的数量,每张发票的长度可能在 1 到 3 页之间。

因此我们无法预先计算页数并将其写入数据库。

我认为您只有一种可能性/解决方案:您必须在每张发票后面写一个空白页,并且在生成整个 PDF 后,您必须使用免费的 PHP 库(如 )对其进行编辑FPDI。与 FPDI 结合使用,甚至可以编辑 PDF 文档。

通过 PDF 编辑,您可以删除所有以奇数页码开头的空白页(如 35 等)。在 FPDI 中,您可以检测页码。它比您现在使用的解决方案要快得多。

您可以使用 FPFI 在 content length 上检测到空白(或空)页面,如下所示:

<?php
require('fpdf.php');
require_once('setasign/Fpdi/autoload.php');

class Pdf extends \setasign\Fpdi\Fpdi
{
    private $pdfReader;
    function Header()
    {
        if(is_null($this->pdfReader))
        {
            $readerId = $this->getPdfReaderId('blank-pages.pdf');
            $this->pdfReader = $this->getPdfReader($readerId);
        }

        $page_fpdi = $this->pdfReader->getPage($this->PageNo());
        $this->content = $page_fpdi->getContentStream();

        $this->Cell(0, 15, 'page content length: '.strlen($this->content));
    }

    protected function _putimages(){}
}

$pdf = new Pdf();
$pdf->SetFont('Arial', '', 12);
$pdf->AddPage(); //page content length: 70 // page with 'Hello World!' string
$pdf->AddPage(); //page content length: 30 // empty page
$pdf->AddPage(); //page content length: 30 // empty page
$pdf->Output();
?>

我的blank-pages.pdf 我使用 FPDF 生成了以下代码:

<?php
require('fpdf.php');

$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');
$pdf->AddPage();
$pdf->AddPage();
$pdf->Output();
?>

【讨论】:

  • 这听起来是个好主意,但据我所知,FPDI 不允许您阅读页面内容。还是我错了?我想尝试一种类似的方法,从页脚读取页码,但我认为我需要第三个库来读取 PDF 内容。您的建议虽然更好,但仍有一个缺陷:如果我在 wkhtmltopdf 中使用页脚,则插入的页面不会完全空白(即它会被编号)。但我想我可以通过在页脚中使用 JS 来减少渲染的总页数并在 page==pageCount 时完全隐藏它来解决这个问题。
  • 更正:页脚实际上不是问题。我暂时忘记了我可以只插入一个没有页脚 html 的单独的空白 HTML 页面/文档。我在想分页符太多了。我依稀记得 FPDI 允许您获取页面的大小。因此,空白可能具有不同的格式,然后替换为正确的尺寸或根据需要删除。但这确实感觉很hacky。但它可能会这样......
  • @PeterTheLobster,为了检测空白页,您可以使用 FPDI 函数 getImportedPageSize()。但是这个空白页的大小必须与其他页面不同。或者,如果您真的需要页面内容,您可以使用SetaPDF-Extractor 组件,它是用 PHP 编写的,允许 PHP 开发人员从现有的 PDF 文档中提取文本内容。但是这个组件不是免费的——200 欧元/项目许可证。
  • @PeterTheLobster,不客气!也谢谢你!
  • 我刚到家。效果很好。我不喜欢它如此骇人听闻的事实,但你能做什么。
猜你喜欢
  • 2012-08-23
  • 1970-01-01
  • 2011-06-11
  • 1970-01-01
  • 2010-09-26
  • 1970-01-01
  • 2023-03-18
  • 2019-10-21
  • 1970-01-01
相关资源
最近更新 更多