一、优化
此次修改优化了pdf工具类已有功能,同时可以用更简单的方式实现更复杂的样式,整体使用方式与前版并无明显区别
1、背景文字生成
修改背景颜色生成位置,起始位置从当前行文字中间改到当前行中间,解决了同一表格下不同行高不同字号表格背景,边框不能完全对齐的问题
Y初始值100 | 行高 | 背景位置y+t/4 忽略相同值t/4 |
100 | 20 | 90-110 |
90 | 10 | 85-95 |
70 | 20 | 60-80 |
该方法在不同行高时存在重叠和空缺问题,不符合主观逻辑,所以对方法进行修改
Y初始值100 | 行高 | 背景位置y+h/2 |
100 | 20 | 100-120 |
90 | 10 | 90-100 |
70 | 20 | 70-90 |
修改后方法可适配各种行高,不存在重叠或空缺问题,同时更符合主观逻辑,但同时需要修改文字展示位置,使文字处于背景中央
文字位置:
文字位置计算公式为y+h/2-t/2 y轴位置+行高h/2-文字高度t/2
2、自动文字大小控制
表格列增加autoSize字段,控制文字在自动宽度下换行是否更改文字大小
3、图片相对定位
为适应图表自动排列分页,修改图表定位方式为相对定位,将图片和文字看做是同一种表格,便于文档内容排版
二、工具类
边框样式
public enum BorderStyle {
SOLID(new float[]{}, 0),
DOTTED(new float[]{1}, 1),
DASHED(new float[]{5,2}, 1);
private final float[] pattern;
private final int phase;
BorderStyle(float[] pattern, int phase) {
this.pattern = pattern;
this.phase = phase;
}
public float[] getPattern() {
return pattern;
}
public int getPhase() {
return phase;
}
}
对齐样式
public enum HorizontalAlignment {
LEFT, CENTER, RIGHT
}
表
@Data
public class PdfTable {
/**
* 行
*/
private List rows;
/**
* 字体
*/
private String font;
/**
* 语言
*/
private String language;
/**
* 页眉
*/
private PdfRow pageHead;
public void addRow(PdfRow row){
rows.add(row);
}
public void addTitleRow(PdfRow row){
pageHead=row;
}
}
行
@Data
@Builder
public class PdfRow {
/**
* 列
*/
private List columns;
/**
* 行高
*/
private float height;
/**
* 边框样式
*/
private BorderStyle borderStyle;
/**
* 边框颜色
*/
@Builder.Default
private Color boderColor=Color.BLACK;
/**
* 下边框是否生效
*/
private Boolean downBorder;
/**
* 上边框是否生效
*/
private Boolean upBorder;
public void addColumn(PdfColumn pdfColumn){
columns.add(pdfColumn);
}
}
文字列
@Data
@Builder
public class PdfColumn {
/**
* 背景颜色
*/
@Builder.Default
private Color backGround = Color.WHITE;
/**
* 边框颜色
*/
@Builder.Default
private Color borderColor = Color.BLACK;
/**
* 文字颜色
*/
@Builder.Default
private Color textColor= Color.BLACK;
/**
* 加粗
*/
@Builder.Default
private Boolean block=false;
/**
* 文字大小
*/
@Builder.Default
private float fontSize=10F;
/**
* 偏移
*/
@Builder.Default
private float offset=0F;
/**
* 文字位置
*/
@Builder.Default
private HorizontalAlignment align =HorizontalAlignment.CENTER;
/**
* 自动宽度
*/
@Builder.Default
private Boolean autoWidth=false;
/**
* 自动换行
*/
@Builder.Default
private Boolean autoLine = false;
/**
* 自动大小
*/
@Builder.Default
private Boolean autoSize=false;
/**
* 左边框
*/
private Boolean leftBorder;
/**
* 右边框
*/
private Boolean rightBorder;
/**
* 名称
*/
private String name;
/**
* 宽度,仅在自动宽度未生效时启用
*/
private float width;
/**
* 图片,图片生效时其他属性均不生效
*/
private PdfColumnImage columnImage;
}
图片列
@Data
@Builder
public class PdfColumnImage {
/**
* 图片
*/
private final byte[] image;
/**
* x轴偏移位置
*/
private float x;
/**
* y轴偏移位置
*/
private float y;
/**
* 图片宽度
*/
private float width;
/**
* 图片高度
*/
private float height;
}
pdf工具类
public class DrawTableUtils {
/**
* 图片路径
*/
private static final String IMAGE_PATH = "image/";
private static final String TARGET_FOLDER = "target";
/**
* 正常字体
*/
private static final String NORMAL_FONT = "AlibabaSans-Regular.otf";
/**
* 加粗字体
*/
private static final String BOLD_FONT = "AlibabaSans-Bold.otf";
/**
* 水平页边距
*/
public static final float LEVEL_PADDING = 35;
/**
* 垂直页边距
*/
public static final float VERTICAL_PADDING = 20;
/**
* 下表格线偏移
*/
private static final float DOWN_LINE_PADDING = 0.5f;
private static final float SIDE_BORDER_PADDING = 1;
/**
* 边框宽度
*/
private static final float BORDER_WIDTH = 1;
/**
* 生成PDF
*
* @param table pdf数据
* @param type PDF类型
* @throws IOException
*/
public static ByteArrayOutputStream createDocument(PdfTable table, ReportType type, String filepath) throws IOException {
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
// 初始化文档
PDDocument document = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
// 字体
String fontName = table.getFont();
InputStream inFont = DrawTableUtils.class.getClassLoader().getResourceAsStream(fontName);
PDType0Font normalFont = PDType0Font.load(document, inFont);
PDType0Font boldFont = null;
// InputStream normalStream = DrawTableUtils.class.getClassLoader().getResourceAsStream(NORMAL_FONT);
// assert normalStream != null;
// OpenTypeFont normalOtfFont = new OTFParser(false, true).parse(normalStream);
// PDType0Font normalFont = PDType0Font.load(document, normalOtfFont, false);
//
// // 加粗字体
// InputStream boldStream = DrawTableUtils.class.getClassLoader().getResourceAsStream(BOLD_FONT);
// assert boldStream != null;
// OpenTypeFont boldOtfFont = new OTFParser(false, true).parse(boldStream);
// PDType0Font boldFont = PDType0Font.load(document, boldOtfFont, false);
// 页
int pageNum = 1;
// 初始化文档位置
Position position = new Position(LEVEL_PADDING, page.getMediaBox().getHeight() - VERTICAL_PADDING, 0);
PDPageContentStream contentStream = new PDPageContentStream(document, page);
for (PdfRow row : table.getRows()) {
// 分页
if (position.startY - row.getHeight() <= VERTICAL_PADDING * 2) {
pageNum++;
PDPage pdPage = new PDPage(PDRectangle.A4);
document.addPage(pdPage);
position.startY = page.getMediaBox().getHeight() - LEVEL_PADDING;
contentStream.close();
contentStream = new PDPageContentStream(document, pdPage);
}
drawLine(row, document, contentStream, position, normalFont, boldFont);
if (row.isDownBorder()) {
drawDownBorder(contentStream, page, row, position);
}
if (row.isUpBorder()) {
drawUpBorder(contentStream, page, row, position);
}
}
contentStream.close();
if (type == ReportType.SINGLE) {
drawPageFootPage(document, pageNum, normalFont);
drawPageHeadPage(document, table.getPageHead(), pageNum, normalFont);
} else if (type == ReportType.PATIENT) {
drawPageFootPage(document, pageNum, normalFont);
drawPageHeadPage(document, table.getPageHead(), pageNum, normalFont);
} else if (type == ReportType.dOUBLE) {
drawPageFootPage(document, pageNum, normalFont);
drawPageHeadPage(document, table.getPageHead(), pageNum, normalFont);
} else if (type == ReportType.HEALTH) {
drawPageFootPage(document, pageNum, normalFont);
}
if (type == ReportType.SINGLE) {
document.save(arrayOutputStream);
} else if (type == ReportType.PATIENT) {
document.save(arrayOutputStream);
} else if (type == ReportType.dOUBLE) {
document.save(arrayOutputStream);
} else if (type == ReportType.HEALTH) {
document.save(arrayOutputStream);
}
document.close();
return arrayOutputStream;
}
/**
* 页眉
*
* @param document 文档
* @param pageHead 页眉
* @param pageNum 文档页数
* @param font 字体
* @throws IOException
*/
private static void drawPageHeadPage(PDDocument document, PdfRow pageHead, int pageNum, PDType0Font font) throws IOException {
for (int i = 1; i < pageNum; i++) {
Position position = new Position(LEVEL_PADDING, PDRectangle.A4.getHeight() - VERTICAL_PADDING, 0);
PDPage page = document.getPage(i);
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
drawLine(pageHead, document, contentStream, position, font, null);
contentStream.close();
}
}
/**
* 生成表格下框,以行为单位
*
* @param contentStream 流
* @param page 页
* @param row 行
* @param position 位置
* @throws IOException
*/
private static void drawDownBorder(PDPageContentStream contentStream, PDPage page, PdfRow row, Position position) throws IOException {
contentStream.setStrokingColor(row.getBoderColor());
contentStream.setLineWidth(BORDER_WIDTH);
contentStream.setLineDashPattern(row.getBorderStyle().getPattern(), row.getBorderStyle().getPhase());
contentStream.moveTo(LEVEL_PADDING, position.startY + DOWN_LINE_PADDING);
contentStream.lineTo(page.getMediaBox().getWidth() - LEVEL_PADDING, position.startY + DOWN_LINE_PADDING);
contentStream.stroke();
}
/**
* 生成表格上边框,以行为单位
* @param contentStream
* @param page 页
* @param row 行
* @param position 位置
* @throws IOException
*/
private static void drawUpBorder(PDPageContentStream contentStream, PDPage page, PdfRow row, Position position) throws IOException {
contentStream.setStrokingColor(row.getBoderColor());
contentStream.setLineWidth(BORDER_WIDTH);
contentStream.setLineDashPattern(row.getBorderStyle().getPattern(), row.getBorderStyle().getPhase());
contentStream.moveTo(LEVEL_PADDING, position.startY + row.getHeight() - DOWN_LINE_PADDING);
contentStream.lineTo(page.getMediaBox().getWidth() - LEVEL_PADDING, position.startY + row.getHeight() - DOWN_LINE_PADDING);
contentStream.stroke();
}
/**
* 生成一行中每一列数据
*
* @param row 行
* @param document 文档
* @param contentStream 流
* @param position 位置
* @param normalFont 正常字体
* @param boldFont 加粗字体
* @throws IOException
*/
private static void drawLine(PdfRow row, PDDocument document, PDPageContentStream contentStream, Position position, PDType0Font normalFont, PDType0Font boldFont) throws IOException {
// 更新Y轴位置
position.startY -= row.getHeight();
for (PdfColumn column : row.getColumns()) {
// 添加图片
if (column.getColumnImage() != null) {
PdfColumnImage columnImage = column.getColumnImage();
// final byte[] imageByte = IOUtils.toByteArray(Objects.requireNonNull(DrawTableUtils.class.getClassLoader().getResourceAsStream(IMAGE_PATH + columnImage.getImage())));
final PDImageXObject image = PDImageXObject.createFromByteArray(document, columnImage.getImage(), "picture");
contentStream.drawImage(image, position.startX + columnImage.getX(), position.startY + columnImage.getY(), columnImage.getWidth(), columnImage.getHeight());
} else {
// 背景
if (column.getBackGround() != Color.WHITE) {
// 文字高度
position.titleHeight = normalFont.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * column.getFontSize();
// 背景高度,取行高
contentStream.setLineWidth(row.getHeight());
contentStream.setStrokingColor(column.getBackGround());
// 实线无间隔
contentStream.setLineDashPattern(BorderStyle.SOLID.getPattern(), BorderStyle.SOLID.getPhase());
// 设置Y轴起始位置,背景占位为当前位置向上高度
contentStream.moveTo(position.startX + column.getOffset(), position.startY + row.getHeight() / 2);
contentStream.lineTo(position.startX + column.getOffset() + column.getWidth(), position.startY + row.getHeight() / 2);
contentStream.stroke();
}
// 左边框
if (column.isLeftBorder()) {
position.titleHeight = normalFont.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * column.getFontSize();
// 设置宽度
contentStream.setLineWidth(BORDER_WIDTH);
contentStream.setStrokingColor(column.getBorderColor());
// 实线无间隔
contentStream.setLineDashPattern(BorderStyle.SOLID.getPattern(), BorderStyle.SOLID.getPhase());
// 设置左边框位置,与背景色对齐
contentStream.moveTo(position.startX + column.getOffset(), position.startY);
contentStream.lineTo(position.startX + column.getOffset(), position.startY + row.getHeight());
contentStream.stroke();
}
// 右边框
if (column.isRightBorder()) {
position.titleHeight = normalFont.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * column.getFontSize();
// 设置宽度
contentStream.setLineWidth(BORDER_WIDTH);
contentStream.setStrokingColor(column.getBorderColor());
// 实线无间隔
contentStream.setLineDashPattern(BorderStyle.SOLID.getPattern(), BorderStyle.SOLID.getPhase());
// 设置左边框位置,与背景色对齐
contentStream.moveTo(position.startX + column.getOffset() + column.getWidth(), position.startY);
contentStream.lineTo(position.startX + column.getOffset() + column.getWidth(), position.startY + row.getHeight());
contentStream.stroke();
}
float titleWidth = normalFont.getStringWidth(column.getName() == null ? "" : column.getName()) / 1000 * column.getFontSize();
// 自动换行
if (column.isAutoLine()) {
if (titleWidth > column.getWidth()) {
// 切分行数
int ceil = (int) Math.ceil(titleWidth / column.getWidth());
int currentCel = ceil;
float hi = row.getHeight() / ceil;
String[] split = column.getName().split(" ");
StringBuilder builder = new StringBuilder();
float len = 0;
for (int i = 0; i < split.length; i++) {
len += normalFont.getStringWidth(split[i] + " ") / 1000 * column.getFontSize();
builder.append(split[i]).append(" ");
if ((i == split.length - 1 && len > 0) || len + normalFont.getStringWidth(split[i + 1]) / 1000 * column.getFontSize() >= column.getWidth()) {
// 写入分行数据
Position aPosition = new Position(position.startX, position.startY + hi * (currentCel - 1), position.getTitleHeight());
PdfColumn aColumn = PdfColumn.builder().align(column.getAlign()).fontSize(column.isAutoSize() ? column.getFontSize() / ceil * 1.8f : column.getFontSize()).name(builder.toString()).width(column.getWidth()).offset(column.getOffset()).block(column.isBlock()).build();
float pWidth = normalFont.getStringWidth(aColumn.getName() == null ? "" : aColumn.getName()) / 1000 * aColumn.getFontSize();
drawMuLine(contentStream, aPosition, aColumn, pWidth, hi, normalFont);
currentCel--;
builder.setLength(0);
len = 0;
}
}
} else {
drawMuLine(contentStream, position, column, titleWidth, row.getHeight(), normalFont);
}
} else {
// 自动宽度
if (column.isAutoWidth()) {
column.setWidth(titleWidth);
}
drawMuLine(contentStream, position, column, titleWidth, row.getHeight(), normalFont);
}
}
// 更新X轴位置
position.startX = position.startX + column.getWidth() + column.getOffset();
}
position.startX = LEVEL_PADDING;
}
/**
* 页脚页数编码
*
* @param document 文档
* @param pageNum 页面数
* @param font 字体
* @throws IOException
*/
private static void drawPageFootPage(PDDocument document, int pageNum, PDType0Font font) throws IOException {
for (int i = 0; i < pageNum; i++) {
String title = "第" + (i + 1) + "页" + "/共" + pageNum + "页";
float titleWidth = font.getStringWidth(title) / 1000 * 10;
PDPage page = document.getPage(i);
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
contentStream.beginText();
contentStream.setFont(font, 10);
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.newLineAtOffset((PDRectangle.A4.getWidth() - titleWidth) / 2, 25);
contentStream.showText(title);
contentStream.endText();
contentStream.close();
}
}
private static void drawMuLine(PDPageContentStream contentStream, Position position, PdfColumn column, float titleWidth, float rowHeight, PDType0Font normalFont) throws IOException {
// 文字
contentStream.beginText();
contentStream.setFont(normalFont, column.getFontSize());
contentStream.setNonStrokingColor(column.getTextColor());
HorizontalAlignment align = column.getAlign();
if (align == HorizontalAlignment.CENTER) {
// 居中
contentStream.newLineAtOffset((column.getWidth() - titleWidth) / 2 + position.startX + column.getOffset(), position.startY - position.titleHeight / 2 + rowHeight / 2);
} else if (align == HorizontalAlignment.LEFT) {
// 居左
contentStream.newLineAtOffset(position.startX + column.getOffset(), position.startY - position.titleHeight / 2 + rowHeight / 2);
} else if (align == HorizontalAlignment.RIGHT) {
// 居右
contentStream.newLineAtOffset(position.startX + column.getOffset() + column.getWidth() - titleWidth, position.startY - position.titleHeight / 2 + rowHeight / 2);
}
contentStream.showText(column.getName() == null ? "" : column.getName());
contentStream.endText();
// 加粗使用偏移方法
if (column.isBlock()) {
drawBlockText(contentStream, position, column, titleWidth, rowHeight);
}
}
/**
* 加粗字体,偏移实现
*
* @param contentStream
* @param position
* @param pdfColumn
* @param titleWidth
* @param rowHeight
* @throws IOException
*/
private static void drawBlockText(PDPageContentStream contentStream, Position position, PdfColumn pdfColumn, float titleWidth, float rowHeight) throws IOException {
float offset = pdfColumn.getFontSize() / 100;
float x = 0;
float y = 0;
if (pdfColumn.getAlign() == HorizontalAlignment.CENTER) {
// 居中
x = (pdfColumn.getWidth() - titleWidth) / 2 + position.startX + pdfColumn.getOffset();
y = position.startY - position.titleHeight / 2 + rowHeight / 2;
} else if (pdfColumn.getAlign() == HorizontalAlignment.LEFT) {
// 居左
x = position.startX + pdfColumn.getOffset();
y = position.startY - position.titleHeight / 2 + rowHeight / 2;
} else if (pdfColumn.getAlign() == HorizontalAlignment.RIGHT) {
// 居右
x = position.startX + pdfColumn.getOffset() + pdfColumn.getWidth() - titleWidth;
y = position.startY - position.titleHeight / 2 + rowHeight / 2;
// offset = -offset;
}
// 左上
contentStream.beginText();
contentStream.newLineAtOffset(x - offset, y + offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 左
contentStream.beginText();
contentStream.newLineAtOffset(x - offset, y);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 左下
contentStream.beginText();
contentStream.newLineAtOffset(x - offset, y - offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 上
contentStream.beginText();
contentStream.newLineAtOffset(x, y + offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 下
contentStream.beginText();
contentStream.newLineAtOffset(x, y - offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 右上
contentStream.beginText();
contentStream.newLineAtOffset(x + offset, y + offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 右
contentStream.beginText();
contentStream.newLineAtOffset(x + offset, y);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// 右下
contentStream.beginText();
contentStream.newLineAtOffset(x + offset, y - offset);
contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
contentStream.endText();
// // 水平
// contentStream.beginText();
// contentStream.newLineAtOffset(x + offset, y);
// contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
// contentStream.endText();
// // 水平上
// contentStream.beginText();
// contentStream.newLineAtOffset(x + offset, y + Math.abs(offset));
// contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
// contentStream.endText();
// // 垂直
// contentStream.beginText();
// contentStream.newLineAtOffset(x, y + Math.abs(offset));
// contentStream.showText(pdfColumn.getName() == null ? "" : pdfColumn.getName());
// contentStream.endText();
}
@Data
@AllArgsConstructor
static class Position {
float startX;
float startY;
float titleHeight;
}
}