voilab/tctable

快速高效地创建表格,无需HTML格式化,并具有高级分页选项

1.2.4 2020-04-23 09:24 UTC

README

安装

通过Composer

在项目根目录创建一个 composer.json 文件

{
    "require": {
        "voilab/tctable": "1.*"
    }
}
$ composer require voilab/tctable

使用方法

这个库的目标不是替换复杂的HTML表格,其中包含行和单元格跨度。它主要用于当你想要显示大量行(如发票等)并确保页面断行在正确位置时。你可以调整孤行数(必须出现在最后一页的最小行数),并使用插件来自定义流程(见下文)。

基本用法

$pdf = new \TCPDF();
$minRowHeight = 6; //mm

$tctable = new \voilab\tctable\TcTable($pdf, $minRowHeight);
$tctable->setColumns([
    'description' => [
        'isMultiLine' => true,
        'header' => 'Description',
        'width' => 100
        // check inline documentation to see what options are available.
        // Basically, it's everything TCPDF Cell and MultiCell can eat.
    ],
    'quantity' => [
        'header' => 'Quantity',
        'width' => 20,
        'align' => 'R'
    ],
    'price' => [
        'header' => 'Price',
        'width' => 20,
        'align' => 'R'
    ]
]);

// get rows data
$rows = getMyDatasAsMyObjs();

// add a page so the content can be printed on something
$pdf->AddPage();
// draw body
$tctable->addBody($rows, function (\voilab\tctable\TcTable $table, \MyObj $row) {
    $change_rate = 0.8;
    // map row data to TcTable column definitions
    return [
        'description' => $row->getDescription(),
        'quantity' => $row->getQuantity(),
        'price' => $row->getPrice() * $change_rate
    ];
});

$pdf->Output('tctable.pdf', 'I');

插件

让列宽度适应剩余页面宽度

$tctable
    ->addPlugin(new \voilab\tctable\plugin\FitColumn('text'))
    ->addColumn('text', [
        'isMultiLine' => true,
        'header' => 'Text'
        // no need to set width here, the plugin will calculate it for us,
        // depending on the other columns width
    ]);

条纹行

$tctable
    // set true to have the first line with colored background
    ->addPlugin(new \voilab\tctable\plugin\StripeRows(true));

孤行管理

// set the minimum elements you want to see on the last page (if any)
$nb = 4;
// set a footer margin. Useful when you have lot of lines, and a total as the
// last one. If you want the total to appears at least with 4 lines, you have
// to add to the pageBreakTrigger margin this line height: the footer
$mFooter = 10; // i.e: mm

$tctable->addPlugin(new \voilab\tctable\plugin\Widows($nb, $mFooter));

调试

TcTable 随附一个调试插件工具,可以显示每个事件传递的数据。

// create the plugin. You can define which events to listen (default to rowadd,
// rowadded, rowskipped, headeradd, headeradded, pageadd and pageadded) and the
// printer object (default to an HTML output with <pre>)
$debug = new \voilab\tctable\plugin\Debug();
$debug
    ->setBounds($fromIndex = 0, $numberOfRows = 2, $dieWhenOutOfBounds = true);

// $dieWhenOutOfBounds will stop the script with die(). Usefull for quick
// debug

$tctable->addPlugin($debug);

你可以通过创建自己的来扩展打印机对象

class MyDebugPrinter implements \voilab\tctable\plugin\debug\PrinterInterface {

    public function output(TcTable $table, array $data) {
        // do something, log, etc.
    }
}

// ... create an instance of debug plugin

// you can set printer the way below, or via the 2nd argument in plugin
// constructor.
$debug->setPrinter(new MyDebugPrinter());

高级插件:在每页末尾为列绘制小计

我们可以进一步计算列的总和,并在页面末尾显示当前总和,最后在下一页报告。

<?php

namespace your\namespace;

use voilab\tctable\TcTable;
use voilab\tctable\Plugin;

class Report implements Plugin {

    // column on which we sum its value
    private $column;
    // the current calculated sum
    private $currentSum = 0;

    public function __construct($column) {
        $this->column = $column;
    }

    public function configure(TcTable $table) {
        $table
            // when launching the main process, reset sum at 0
            ->on(TcTable::EV_BODY_ADD, [$this, 'resetSum'])
            // after each added row, add the value to the sum
            ->on(TcTable::EV_ROW_ADDED, [$this, 'makeSum'])
            // when a page is added, draw the "subtotal" string
            ->on(TcTable::EV_PAGE_ADD, [$this, 'addSubTotal'])
            // after a page is added, draw the "sum from previous page" string
            ->on(TcTable::EV_PAGE_ADDED, [$this, 'addReport']);
    }

    public function resetSum() {
        $this->currentSum = 0;
    }

    public function makeSum(TcTable $table, $data) {
        $this->currentSum += $data[$this->column] * 1;
    }

    public function getSum() {
        return $this->currentSum;
    }

    public function addSubTotal(TcTable $table) {
        $pdf = $table->getPdf();
        $pdf->SetFont('', 'I');
        $pdf->SetTextColor(150, 150, 150);
        $pdf->Cell($table->getColumnWidthUntil($this->column), $table->getColumnHeight(), "Sub-total:", false, false, 'R');
        $pdf->Cell($table->getColumnWidth($this->column), $table->getColumnHeight(), $this->currentSum, false, false, 'R');
        $pdf->SetTextColor(0, 0, 0);
        $pdf->SetFont('', '');
    }

    public function addReport(TcTable $table) {
        $pdf = $table->getPdf();
        $pdf->SetFont('', 'I');
        $pdf->SetTextColor(150, 150, 150);
        $table->getPdf()->Cell($table->getColumnWidthUntil($this->column), $table->getColumnHeight(), "Sum from previous page", 'B', false, 'R');
        $table->getPdf()->Cell($table->getColumnWidth($this->column), $table->getColumnHeight(), $this->currentSum, 'B', true, 'R');
        $pdf->SetTextColor(0, 0, 0);
        $pdf->SetFont('', '');
    }

}

以及TcTable

$tctable->addPlugin(new \your\namespace\Report('total'));

自定义事件

TcTable 触发一些我们可以监听的事件。插件大量使用它们。但你可以简单地定义事件,而不需要插件。这允许我们添加一些有用的方法。

在每一页上添加标题

use \voilab\tctable\TcTable;
// ... create tctable

$tctable
    // when a page is added, draw headers
    ->on(TcTable::EV_PAGE_ADDED, function(TcTable $t) {
        $t->addHeader();
    })
    // just before headers are drawn, set font style to bold
    ->on(TcTable::EV_HEADER_ADD, function(TcTable $t) {
        $t->getPdf()->SetFont('', 'B');
    })
    // after headers are drawn, reset the font style
    ->on(TcTable::EV_HEADER_ADDED, function(TcTable $t) {
        $t->getPdf()->SetFont('', '');
    });

渲染器和主体函数

在解析数据时,你可以为每个列或某些列定义渲染函数,或者在调用 $tctable->addBody() 时使用匿名函数。这些函数被调用两次,因为在高度计算过程中需要。在某些情况下,你需要考虑这一点。

$total = 0;
$tctable->addBody($rows, function (TcTable $t, $row, $index, $isHeightCalculation) use (&$total) {
    // if $height is true, it means this method is called when height
    // calculation is running. If we want to do a sum, we check first if
    // $isHeightCalculation is false, so it means the func is called during row
    // draw.
    if (!$isHeightCalculation) {
        $total += $row->getSomeValue();
    }
    // you still need to return the data regardless of $isHeightCalculation
    return [
        'description' => $row->getDescription(),
        'value' => $row->getSomeValue()
    ];
});

echo sprintf('Total: %d', $total);

相同的概念也适用于列渲染器。

注意 在上述情况(创建总和)中,你最好使用插件或事件。使用事件 TcTable::EV_ROW_ADDED,你可以完全做到同样的事情,而无需担心高度计算(见下文)。

$total = 0;
$tctable
    ->on(TcTable::EV_ROW_ADDED, function (TcTable $t, $row) use (&$total) {
        $total += $row->getSomeValue();
    })
    ->addBody($rows, function (TcTable $t, $row) {
        return [
            'description' => $row->getDescription(),
            'value' => $row->getSomeValue()
        ];
    });

echo sprintf('Total: %d', $total);

优化

如果你确切知道每行的高度,你可以通过这种方式优化工作流程,从而绕过高度计算

$tctable->on(TcTable::EV_ROW_HEIGHT_GET, function (TcTable $t, $row, $index) {
    return 6.4; // or null for default calculation
});

如果你想更改单元格高度的计算方式,你可以通过这种方式覆盖默认行为

$tctable->on(TcTable::EV_CELL_HEIGHT_GET, function (TcTable $t, $column, $data, $row) {
    if ($column == 'specialColumn') {
        return 12.8;
    }
    // use default calculation
    return null;
});

请记住,只有多行单元格会被检查其高度。其他单元格在过程中不被考虑。

自定义绘图函数

如果你需要插入图像或需要对单元格的绘图进行非常具体的事情,你可以通过这种方式绕过正常的绘图函数

$tctable->addColumn('specialColumn', [
    'header' => "Special column",
    'width' => 15,
    'drawFn' => function (TcTable $t, $data, array $definition, $column, $row) {
        $t->getPdf()->Image(); //configure TCPDF Image method your way
        // return false to draw the cell normally, if there's no image to
        // display, for example
    },
    'drawHeaderFn' => function (TcTable $t, $data, array $definition, $column, $row) {
        // same comments as above
    }
]);

测试

尚未编写测试...

$ phpunit

安全性

如果你发现任何与安全相关的问题,请使用问题跟踪器。

致谢

许可证

MIT许可证(MIT)。请参阅许可证文件以获取更多信息。