在 itext7 中更灵活地为 table 绘制自定义边框
Draw custom borders for table with more flexibility in itext7
之前我问了一个关于绘制自定义 table 边框的问题:
并且提供了一种绘制自定义边框的方法的答案,但它不允许影响完整的行或列线(例如稍微倾斜),因为单元格边框是为每个单元格单独绘制的。
我想给 table 边框添加一些随机性,如下面的屏幕截图所示:
这是我的代码,适用于适合单个页面的 tables,但如果 table 跨越多个页面,代码将不再有效:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc);
doc.add(new Paragraph("Table event"));
Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
table.setNextRenderer(new DottedLineTableRenderer(table, new Table.RowRange(0, 0)));
String s ="";
for(int i=0;i<35;i++){
s+="\nTest";
}
table.addCell(new Cell().add(new Paragraph(s)).setBorder(Border.NO_BORDER));
table.addCell(new Cell().add(new Paragraph("A2")).setBorder(Border.NO_BORDER));
table.addCell(new Cell().add(new Paragraph("A3")).setBorder(Border.NO_BORDER));
doc.add(table);
doc.close();
private class DottedLineTableRenderer extends TableRenderer {
public DottedLineTableRenderer(Table modelElement, Table.RowRange rowRange) {
super(modelElement, rowRange);
}
@Override
public void drawChildren(DrawContext drawContext) {
super.drawChildren(drawContext);
PdfCanvas canvas = drawContext.getCanvas();
int maxLineTo = 5;
int minLineTo = 2;
int lineToHorizontalLine = (int)(Math.random() * maxLineTo) + minLineTo;
int maxSkewHor = 5;
int minSkewHor = 2;
int skewHorizontalLine = (int)(Math.random() * maxSkewHor) + minSkewHor;
int maxVerticalLine = 5;
int minVerticalLine = 2;
int lineToVerticalLine = (int)(Math.random() * maxVerticalLine) + minVerticalLine;
canvas.setLineWidth(2).setStrokeColor(new DeviceRgb(222, 27, 27));
// first horizontal line
CellRenderer[] cellRenderers = rows.get(0);
canvas.moveTo(cellRenderers[0].getOccupiedArea().getBBox().getLeft()-lineToHorizontalLine,
cellRenderers[0].getOccupiedArea().getBBox().getTop());
canvas.lineTo(cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getRight()+lineToHorizontalLine,
cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getTop());
for (int i = 0; i < rows.size(); i++) {
skewHorizontalLine = (int)(Math.random() * maxSkewHor) + minSkewHor;
lineToHorizontalLine = (int)(Math.random() * maxLineTo) + minLineTo;
cellRenderers = rows.get(i);
// horizontal lines
canvas.moveTo(cellRenderers[0].getOccupiedArea().getBBox().getX()-lineToHorizontalLine,
cellRenderers[0].getOccupiedArea().getBBox().getY()+skewHorizontalLine);
canvas.lineTo(cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getRight()+lineToHorizontalLine,
cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getBottom());
// first vertical line
Rectangle cellRect = cellRenderers[0].getOccupiedArea().getBBox();
canvas.moveTo(cellRect.getLeft(), cellRect.getBottom());
canvas.lineTo(cellRect.getLeft(), cellRect.getTop()+lineToVerticalLine );
// vertical lines
for (int j = 0; j < cellRenderers.length; j++) {
lineToVerticalLine = (int)(Math.random() * maxVerticalLine) + minVerticalLine;
cellRect = cellRenderers[j].getOccupiedArea().getBBox();
canvas.moveTo(cellRect.getRight(), cellRect.getBottom()-lineToVerticalLine);
canvas.lineTo(cellRect.getRight(), cellRect.getTop()+lineToVerticalLine); //ячейки
}
}
canvas.stroke();
}
}
我想自己画自定义边框:)
首先,我们需要正确重写渲染器,即重写getNextRenderer()
方法。目前 TableRenderer
的覆盖问题很大,因为 TableRenderer
的无参数构造函数不可访问,而其他构造函数会执行一些更改状态的隐式工作。但是我们仍然可以使用以下代码解决这个问题:
@Override
public IRenderer getNextRenderer() {
CustomTableRenderer nextTable = new CustomTableRenderer((Table) modelElement);
nextTable.rows.clear();
nextTable.rowRange = null;
return nextTable;
}
免责声明:由于答案使用了 TableRenderer
的私有实现细节,因此将来可能无法使用。它与 7.1.6
一起工作,后者是撰写本文时最新发布的版本 post。您应该为此目的创建代码的自定义分支。 Pull requests 也欢迎。
如果我们查看 TableRenderer
的实现,我们会看到 class 包含 heights
(line in code) and countedColumnWidth
(line in code) fields which sound interesting, but they are private
. It means that we should create our custom fork of iText (source code available at https://github.com/itext/itext7),使这些字段成为 protected
并使用他们在我们的子class.
您可以在代码中使用反射,但风险自负,但不应使用它,因为它可能不适用于您的 JVM(强烈建议不要更改可访问性修饰符)或者可能无法在下一版本的 iText 中使用,或者可能由于其他原因无法使用。我不会在我的答案中添加反射代码以进一步阻止它的使用。
我们需要做的就是覆盖 drawBorders()
方法。这是已经为行添加了一些随机性的代码。
private static class CustomTableRenderer extends TableRenderer {
public CustomTableRenderer(Table modelElement) {
super(modelElement);
}
@Override
public IRenderer getNextRenderer() {
CustomTableRenderer nextTable = new CustomTableRenderer((Table) modelElement);
nextTable.rows.clear();
nextTable.rowRange = null;
return nextTable;
}
@Override
protected void drawBorders(DrawContext drawContext) {
PdfCanvas canvas = drawContext.getCanvas();
canvas.saveState();
canvas.setStrokeColor(ColorConstants.RED);
Random r = new Random();
// Draw vertical lines
float curX = getOccupiedAreaBBox().getLeft();
for (int i = 0; i <= countedColumnWidth.length; i++) {
canvas.moveTo(curX, getOccupiedAreaBBox().getTop() + 3);
canvas.lineTo(curX + r.nextInt(4), getOccupiedAreaBBox().getBottom() - 3);
if (i < countedColumnWidth.length) {
float curWidth = countedColumnWidth[i];
curX += curWidth;
}
}
// Draw horizontal lines
float curY = getOccupiedAreaBBox().getBottom();
for (int i = 0; i <= heights.size(); i++) {
canvas.moveTo(getOccupiedAreaBBox().getLeft() - 3, curY);
canvas.lineTo(getOccupiedAreaBBox().getRight() + 3, curY + r.nextInt(4));
if (i < heights.size()) {
float curHeight = heights.get(i);
curY += curHeight;
}
}
canvas.stroke();
canvas.restoreState();
}
}
要激活自定义渲染器,请在添加到文档之前将其设置为 table:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
Document doc = new Document(pdfDoc);
Table table = new Table(UnitValue.createPercentArray(new float[]{30, 30}));
for (int i = 0; i < 40; i++) {
table.addCell(new Cell().add(new Paragraph("Hello")));
table.addCell(new Cell().add(new Paragraph("World")));
table.startNewRow();
}
table.setNextRenderer(new CustomTableRenderer(table));
doc.add(table);
这就是结果 table 的样子:
之前我问了一个关于绘制自定义 table 边框的问题:
并且提供了一种绘制自定义边框的方法的答案,但它不允许影响完整的行或列线(例如稍微倾斜),因为单元格边框是为每个单元格单独绘制的。
我想给 table 边框添加一些随机性,如下面的屏幕截图所示:
这是我的代码,适用于适合单个页面的 tables,但如果 table 跨越多个页面,代码将不再有效:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc);
doc.add(new Paragraph("Table event"));
Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
table.setNextRenderer(new DottedLineTableRenderer(table, new Table.RowRange(0, 0)));
String s ="";
for(int i=0;i<35;i++){
s+="\nTest";
}
table.addCell(new Cell().add(new Paragraph(s)).setBorder(Border.NO_BORDER));
table.addCell(new Cell().add(new Paragraph("A2")).setBorder(Border.NO_BORDER));
table.addCell(new Cell().add(new Paragraph("A3")).setBorder(Border.NO_BORDER));
doc.add(table);
doc.close();
private class DottedLineTableRenderer extends TableRenderer {
public DottedLineTableRenderer(Table modelElement, Table.RowRange rowRange) {
super(modelElement, rowRange);
}
@Override
public void drawChildren(DrawContext drawContext) {
super.drawChildren(drawContext);
PdfCanvas canvas = drawContext.getCanvas();
int maxLineTo = 5;
int minLineTo = 2;
int lineToHorizontalLine = (int)(Math.random() * maxLineTo) + minLineTo;
int maxSkewHor = 5;
int minSkewHor = 2;
int skewHorizontalLine = (int)(Math.random() * maxSkewHor) + minSkewHor;
int maxVerticalLine = 5;
int minVerticalLine = 2;
int lineToVerticalLine = (int)(Math.random() * maxVerticalLine) + minVerticalLine;
canvas.setLineWidth(2).setStrokeColor(new DeviceRgb(222, 27, 27));
// first horizontal line
CellRenderer[] cellRenderers = rows.get(0);
canvas.moveTo(cellRenderers[0].getOccupiedArea().getBBox().getLeft()-lineToHorizontalLine,
cellRenderers[0].getOccupiedArea().getBBox().getTop());
canvas.lineTo(cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getRight()+lineToHorizontalLine,
cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getTop());
for (int i = 0; i < rows.size(); i++) {
skewHorizontalLine = (int)(Math.random() * maxSkewHor) + minSkewHor;
lineToHorizontalLine = (int)(Math.random() * maxLineTo) + minLineTo;
cellRenderers = rows.get(i);
// horizontal lines
canvas.moveTo(cellRenderers[0].getOccupiedArea().getBBox().getX()-lineToHorizontalLine,
cellRenderers[0].getOccupiedArea().getBBox().getY()+skewHorizontalLine);
canvas.lineTo(cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getRight()+lineToHorizontalLine,
cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getBottom());
// first vertical line
Rectangle cellRect = cellRenderers[0].getOccupiedArea().getBBox();
canvas.moveTo(cellRect.getLeft(), cellRect.getBottom());
canvas.lineTo(cellRect.getLeft(), cellRect.getTop()+lineToVerticalLine );
// vertical lines
for (int j = 0; j < cellRenderers.length; j++) {
lineToVerticalLine = (int)(Math.random() * maxVerticalLine) + minVerticalLine;
cellRect = cellRenderers[j].getOccupiedArea().getBBox();
canvas.moveTo(cellRect.getRight(), cellRect.getBottom()-lineToVerticalLine);
canvas.lineTo(cellRect.getRight(), cellRect.getTop()+lineToVerticalLine); //ячейки
}
}
canvas.stroke();
}
}
我想自己画自定义边框:)
首先,我们需要正确重写渲染器,即重写getNextRenderer()
方法。目前 TableRenderer
的覆盖问题很大,因为 TableRenderer
的无参数构造函数不可访问,而其他构造函数会执行一些更改状态的隐式工作。但是我们仍然可以使用以下代码解决这个问题:
@Override
public IRenderer getNextRenderer() {
CustomTableRenderer nextTable = new CustomTableRenderer((Table) modelElement);
nextTable.rows.clear();
nextTable.rowRange = null;
return nextTable;
}
免责声明:由于答案使用了 TableRenderer
的私有实现细节,因此将来可能无法使用。它与 7.1.6
一起工作,后者是撰写本文时最新发布的版本 post。您应该为此目的创建代码的自定义分支。 Pull requests 也欢迎。
如果我们查看 TableRenderer
的实现,我们会看到 class 包含 heights
(line in code) and countedColumnWidth
(line in code) fields which sound interesting, but they are private
. It means that we should create our custom fork of iText (source code available at https://github.com/itext/itext7),使这些字段成为 protected
并使用他们在我们的子class.
您可以在代码中使用反射,但风险自负,但不应使用它,因为它可能不适用于您的 JVM(强烈建议不要更改可访问性修饰符)或者可能无法在下一版本的 iText 中使用,或者可能由于其他原因无法使用。我不会在我的答案中添加反射代码以进一步阻止它的使用。
我们需要做的就是覆盖 drawBorders()
方法。这是已经为行添加了一些随机性的代码。
private static class CustomTableRenderer extends TableRenderer {
public CustomTableRenderer(Table modelElement) {
super(modelElement);
}
@Override
public IRenderer getNextRenderer() {
CustomTableRenderer nextTable = new CustomTableRenderer((Table) modelElement);
nextTable.rows.clear();
nextTable.rowRange = null;
return nextTable;
}
@Override
protected void drawBorders(DrawContext drawContext) {
PdfCanvas canvas = drawContext.getCanvas();
canvas.saveState();
canvas.setStrokeColor(ColorConstants.RED);
Random r = new Random();
// Draw vertical lines
float curX = getOccupiedAreaBBox().getLeft();
for (int i = 0; i <= countedColumnWidth.length; i++) {
canvas.moveTo(curX, getOccupiedAreaBBox().getTop() + 3);
canvas.lineTo(curX + r.nextInt(4), getOccupiedAreaBBox().getBottom() - 3);
if (i < countedColumnWidth.length) {
float curWidth = countedColumnWidth[i];
curX += curWidth;
}
}
// Draw horizontal lines
float curY = getOccupiedAreaBBox().getBottom();
for (int i = 0; i <= heights.size(); i++) {
canvas.moveTo(getOccupiedAreaBBox().getLeft() - 3, curY);
canvas.lineTo(getOccupiedAreaBBox().getRight() + 3, curY + r.nextInt(4));
if (i < heights.size()) {
float curHeight = heights.get(i);
curY += curHeight;
}
}
canvas.stroke();
canvas.restoreState();
}
}
要激活自定义渲染器,请在添加到文档之前将其设置为 table:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
Document doc = new Document(pdfDoc);
Table table = new Table(UnitValue.createPercentArray(new float[]{30, 30}));
for (int i = 0; i < 40; i++) {
table.addCell(new Cell().add(new Paragraph("Hello")));
table.addCell(new Cell().add(new Paragraph("World")));
table.startNewRow();
}
table.setNextRenderer(new CustomTableRenderer(table));
doc.add(table);
这就是结果 table 的样子: