package com.ruoyi.common.utils; import com.itextpdf.text.*; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import java.io.IOException; import java.io.OutputStream; import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * PDF写入器 * @author gxk */ public class PdfBaseWriter extends Document{ // ---------------------------------默认参数--------------------------------- /** * 中文字体 */ public final String FONT_NAME_ST_SONG_LIGHT = "STSong-Light"; /** * 中文字体编码 */ public final String FONT_ENCODING_UNI_GB_UCS2_H = "UniGB-UCS2-H"; //---------------------------------常用字体/颜色--------------------------------- /** * 灰色 */ public final BaseColor GREY_COLOR = new BaseColor(139, 134, 130); /** * 浅黄色 */ public final BaseColor LIGHT_YELLOW_COLOR = new BaseColor(255, 255, 153); /** * 灰色 */ public final BaseColor LIGHT_GRAY_COLOR = new BaseColor(241, 241, 241); /** * 浅蓝色 */ public final BaseColor LIGHT_BLUE_COLOR = new BaseColor(133, 188, 224); /** * 深蓝色 */ public final BaseColor HARD_BLUE_COLOR = new BaseColor(31, 86, 112); /** * 创建页面大小为A4的PDF * @param os PDF写入流 * @throws DocumentException */ public PdfBaseWriter(OutputStream os) throws DocumentException { super(); PdfWriter.getInstance(this, os); } /** * 创建PDF * @param os PDF写入流 * @param pageSize 页面大小(PageSize.A4等) * @throws DocumentException */ public PdfBaseWriter(OutputStream os, Rectangle pageSize) throws DocumentException { super(pageSize); PdfWriter.getInstance(this, os); } //------------------------------------表------------------------------------ /** * 写入表格,表格数据集合中第一行数据 key 为表格表头 * @param rows 表格数据集合 * @return 表格 {@link PdfPTable} */ public PdfPTable writeTable(ArrayList<LinkedHashMap<String, Object>> rows) { if (rows == null) { return null; } // 根据写入数据宽度创建表格 List<String> headers = new ArrayList<String>(rows.get(0).keySet()); PdfPTable table = createWithHeaderTable(headers, null); for (LinkedHashMap<String, Object> row : rows) { for (String k : headers) { String text = String.valueOf(row.get(k)); writeCell(String.valueOf(text), calculateColumnNumber(k), 1, table); } } addContent(table); return table; } /** * 创建带表头的表格 {@link PdfPTable} * @param headers 表头数据集 * @param columnWidths 每一列的宽度 * @return 表格 {@link PdfPTable} */ public PdfPTable createWithHeaderTable(List<String> headers, float[] columnWidths) { PdfPTable headerTable = null; if (columnWidths == null) { int numColumns = calculateColumnNumber(headers); headerTable = new PdfPTable(numColumns); } else { headerTable = new PdfPTable(columnWidths); } headerTable.setWidthPercentage(100); headerTable.setSpacingBefore(10); for (String text : headers) { if (columnWidths == null) { writeCell(text, Element.ALIGN_CENTER, calculateColumnNumber(text), 1, headerTable, defaultFont(), 0f, Rectangle.BOX, LIGHT_BLUE_COLOR); } else { writeCell(text, Element.ALIGN_CENTER, 1, 1, headerTable, defaultFont(), 0f, Rectangle.BOX, LIGHT_GRAY_COLOR); } } return headerTable; } /** * 设置空白行 * @param table 表 {@link PdfPTable} * @param bgColor 背景颜色 {@link BaseColor} * @param columnNumber 列号 * @param fixedHeight 固定高度 */ public void setBlankRow(PdfPTable table, BaseColor bgColor, int columnNumber, float fixedHeight) { Paragraph paragraph = new Paragraph(""); PdfPCell pdfPCell = newPdfPCell(bgColor, 0, 1, columnNumber, 1, paragraph); // 单元格固定高度 pdfPCell.setFixedHeight(fixedHeight); table.addCell(pdfPCell); } //------------------------------------单元格------------------------------------ /** * 写入无边框单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeNoBorderCell(String text, int align, int colspan, int rowspan, PdfPTable table) { return writeCell(text, align, colspan, rowspan, table, defaultFont(), 0f, Rectangle.NO_BORDER, BaseColor.WHITE); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int colspan, int rowspan, PdfPTable table) { return writeCell(text, Element.ALIGN_CENTER, colspan, rowspan, table, defaultFont(), 0f, Rectangle.BOX, BaseColor.WHITE); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table) { return writeCell(text, align, colspan, rowspan, table, defaultFont(), 0f, Rectangle.BOX, BaseColor.WHITE); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeBottomCell(String text, int align, int colspan, int rowspan, PdfPTable table) { return writeCell(text, align, colspan, rowspan, table, defaultFont(), 0f, Rectangle.BOTTOM, BaseColor.WHITE); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @param font 字体 {@link Font} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font) { return writeCell(text, align, colspan, rowspan, table, font, 0f, Rectangle.BOX); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @param font 字体 {@link Font} * @param paddingLeft 左边距 * @param border 边框 {@link Rectangle} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border) { return writeCell(text, align, colspan, rowspan, table, font, paddingLeft, border, true, BaseColor.WHITE); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @param font 字体 {@link Font} * @param paddingLeft 左边距 * @param border 边框 {@link Rectangle} * @param bgColor 背景颜色 {@link BaseColor} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border, BaseColor bgColor) { return writeCell(text, align, colspan, rowspan, table, font, paddingLeft, border, true, bgColor); } /** * 写入单元格 {@link PdfPCell} * @param text 内容 * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param table 表 {@link PdfPTable} * @param font 字体 {@link Font} * @param paddingLeft 左边距 * @param border 边框 {@link Rectangle} * @param isSet 是否已经设置 * @param bgColor 背景颜色 {@link BaseColor} * @return 单元格 {@link PdfPCell} */ public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border, boolean isSet, BaseColor bgColor) { if (text == null) { text = ""; } // 包含中文,则使用中文字体 if (isChinese(text)) { font = chineseFont(font.getSize(), font.getStyle(), font.getColor()); } Paragraph elements = new Paragraph(text, font); elements.setAlignment(align); PdfPCell cell = newPdfPCell(bgColor, border, align, colspan, rowspan, elements); if (paddingLeft != null) { cell.setPaddingLeft(paddingLeft); } if (align == Element.ALIGN_LEFT) { cell.setPaddingLeft(4); } cell.setPaddingBottom(10); if (isSet) { table.addCell(cell); } return cell; } /** * 创建新的单元格 {@link PdfPCell} * @param bgColor 背景颜色 {@link BaseColor} * @param border 边框 {@link Rectangle} * @param align 对齐方式 {@link Element} * @param colspan 所占列数 * @param rowspan 所占行数 * @param paragraph 段落 {@link Paragraph} * @return 单元格 {@link PdfPCell} */ public PdfPCell newPdfPCell(BaseColor bgColor, int border, int align, int colspan, int rowspan, Paragraph paragraph) { PdfPCell cell = new PdfPCell(); cell.setBorderColor(BaseColor.BLACK); cell.setBorderColorLeft(BaseColor.BLACK); cell.setBorderColorRight(BaseColor.BLACK); cell.setBackgroundColor(bgColor); cell.setBorder(border); // 水平居中 cell.setHorizontalAlignment(align); // 垂直居中 cell.setVerticalAlignment(Element.ALIGN_MIDDLE); cell.setColspan(colspan); cell.setRowspan(rowspan); cell.setMinimumHeight(10); cell.addElement(paragraph); // 设置单元格的边框宽度和颜色 cell.setBorderWidth(0.5f); cell.setBorderColor(GREY_COLOR); return cell; } /** * 创建内容为图片的单元格 {@link PdfPCell} * @param colspan 所占列数 * @param rowspan 所占行数 * @param image 内容(文字或图片对象) {@link Image} * @param fitWidth 宽度自适应 * @param fitHeight 高度自适应 * @return 单元格 {@link PdfPCell} */ public PdfPCell newPdfPCellOfImage(int colspan, int rowspan, Image image, float fitWidth, float fitHeight) { PdfPCell cell = new PdfPCell(); cell.setUseAscender(Boolean.TRUE); cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); cell.setBorder(Rectangle.BOX); cell.setBorderColor(BaseColor.GRAY); cell.setBackgroundColor(BaseColor.WHITE); cell.setColspan(colspan); cell.setRowspan(rowspan); image.scaleToFit(fitWidth, fitHeight); cell.addElement(image); return cell; } /** * 把图文混合写入单元格 * @param list * @param table * @return */ public PdfPCell writeTextAndImageCell(List<String> list, PdfPTable table) throws DocumentException, IOException { PdfPCell cell = new PdfPCell(); cell.setBorderColor(BaseColor.GRAY); for (String value : list) { if (value.contains("/") && (value.endsWith(".jpg") || value.endsWith(".png") || value.endsWith(".jpeg"))) { Image image = Image.getInstance(value); cell.addElement(image); } else { cell.addElement(createTextInImageCell(value)); } } table.addCell(cell); return cell; } //------------------------------------文本内容------------------------------------ /** * 设置一级标题 * @param title 章节标题 * @param number 章节编号 * @return 返回当前标题 {@link Chapter} */ public Chapter setTitle1(String title, int number) { Paragraph paragraph = getParagraph(2, title); Chapter chapter = new Chapter(paragraph, number); chapter.setTriggerNewPage(false); addContent(chapter); return chapter; } /** * 设置二级标题 * @param chapter 对应一级标题 {@link Chapter} * @param title 章节标题 * @return 返回当前标题 {@link Section} */ public Section setTitle2(Chapter chapter, String title) { Paragraph paragraph = getParagraph(3, title); Section section = chapter.addSection(paragraph); section.setNumberStyle(Section.NUMBERSTYLE_DOTTED_WITHOUT_FINAL_DOT); addContent(section); return section; } /** * 设置三级标题 * @param section 对应二级标题 {@link Section} * @param title 章节标题 */ public void setTitle3(Section section, String title) { Paragraph paragraph = getParagraph(4, title); Section subsection = section.addSection(paragraph); subsection.setNumberStyle(Section.NUMBERSTYLE_DOTTED); addContent(subsection); } /** * 写出段落正文 * @param text 正文内容 */ public void writeText(String text) { writeText(text, defaultFont(), true); } /** * 写出段落正文 * @param text 正文内容 * @param font 字体 {@link Font} */ public void writeText(String text, Font font) { writeText(text, font, true); } /** * 写出段落正文 * @param text 正文内容 * @param font 字体 {@link Font} * @param firstLineIndent 首行是否需要缩进 */ public void writeText(String text, Font font, boolean firstLineIndent) { Paragraph paragraph = new Paragraph(text, font); if (firstLineIndent) { paragraph.setFirstLineIndent(24); } addContent(paragraph); } /** * 写出段落正文 * @param text 正文内容 * @param font 字体 {@link Font} * @param alignment 设置此段落的对齐方式 {@link Element} */ public void writeText(String text, Font font, int alignment) { Paragraph paragraph = new Paragraph(text, font); paragraph.setAlignment(alignment); addContent(paragraph); } /** * 写出列表 * @param texts 列表内容集合 * @param wantNumbering 是否需要序号 */ public void writeList(List<String> texts, boolean wantNumbering) { if (texts == null || texts.size() == 0) { return; } int i = 1; for (String text : texts) { if (wantNumbering) { writeText(i + "、" + text, defaultFont(), false); }else { writeText(text, defaultFont(), false); } i++; } } /** * 写出列表 * @param formData 列表内容集合 * @param tagValue value 值是否需要下划线 */ public void writeList(Map<String, String> formData, boolean tagValue) { if (formData == null || formData.size() == 0) { return; } for (String v : formData.keySet()) { addContent(new Phrase(v + ":", defaultFont())); Phrase phrase = new Phrase(); Chunk chunk = new Chunk(formData.get(v), defaultFont()); if (tagValue) { chunk.setUnderline(0.2f, -2f); } phrase.add(chunk); addContent(phrase); addContent(Chunk.NEWLINE); } } /** * 添加key-value似的内容 * @param frontText 前内容 * @param behindText 后内容 * @param size 大小 * @param firstLineIndent 第一行缩进 * @param spacingAfter 段间距 */ public void writeUnderlineText(String frontText, String behindText, Float size, int firstLineIndent, Float spacingAfter) { Paragraph paragraph = new Paragraph(); paragraph.setFirstLineIndent(firstLineIndent); paragraph.setSpacingAfter(spacingAfter); Phrase frontPhrase = new Phrase(frontText, defaultFont(size)); Phrase behindPhrase = new Phrase(); Chunk chunk = new Chunk(behindText, defaultFont(size)); chunk.setUnderline(01.f, -5f); behindPhrase.add(chunk); paragraph.add(frontPhrase); paragraph.add(behindPhrase); addContent(paragraph); } // /** // * 标题 // * @param content // * @return // * @throws IOException // * @throws DocumentException // */ // public static Paragraph createTitle(String content) throws IOException, DocumentException { // Font font = new Font(getBaseFont(), 24, Font.BOLD); // Paragraph paragraph = new Paragraph(content, font); // paragraph.setAlignment(Element.ALIGN_CENTER); // return paragraph; // } // // /** // * H1标题 // * @param content // * @return // * @throws IOException // * @throws DocumentException // */ // public static Paragraph createChapterH1(String content) throws IOException, DocumentException { // Font font = new Font(getBaseFont(), 22, Font.BOLD); // Paragraph paragraph = new Paragraph(content, font); // paragraph.setAlignment(Element.ALIGN_LEFT); // return paragraph; // } // // /** // * H2标题 // * @param content // * @return // * @throws IOException // * @throws DocumentException // */ // public static Paragraph createChapterH2(String content) throws IOException, DocumentException { // Font font = new Font(getBaseFont(), 18, Font.BOLD); // Paragraph paragraph = new Paragraph(content, font); // paragraph.setAlignment(Element.ALIGN_LEFT); // // 设置段落上空白 // paragraph.setSpacingAfter(5f); // return paragraph; // } // // /** // * 段 // * @param content // * @return // * @throws IOException // * @throws DocumentException // */ // public static Paragraph createParagraph(String content) throws IOException, DocumentException { // Paragraph paragraph = new Paragraph(content, defaultFont()); // paragraph.setAlignment(Element.ALIGN_LEFT); // // 设置左缩进 // paragraph.setIndentationLeft(12); // // 设置右缩进 // paragraph.setIndentationRight(12); // // 设置首行缩进 // paragraph.setFirstLineIndent(24); // // 行间距 // paragraph.setLeading(20f); // // 设置段落上空白 // paragraph.setSpacingBefore(5f); // // 设置段落下空白 // paragraph.setSpacingAfter(10f); // return paragraph; // } /** * 创建文本 * @param type 1-标题 2-标题一 3-标题二 4-标题三 5-正文 6-正文左对齐 */ public Paragraph getParagraph(int type, String text) { Font font = new Font(defaultFont()); if (1 == type) { font.setSize(22f); font.setStyle(Font.BOLD); } else if(2 == type) { font.setSize(16f); font.setStyle(Font.BOLD); } else if(3 == type) { font.setSize(14f); font.setStyle(Font.BOLD); } else if(4 == type) { font.setSize(14f); } else if(5 == type) { font.setSize(10.5f); } else if(6 == type) { font.setSize(10.5f); } else { font.setSize(10.5f); } Paragraph paragraph = new Paragraph(text, font); if (1 == type) { paragraph.setAlignment(Paragraph.ALIGN_CENTER); paragraph.setSpacingBefore(10f); paragraph.setSpacingAfter(10f); } else if(2 == type) { paragraph.setAlignment(Element.ALIGN_JUSTIFIED); paragraph.setSpacingBefore(2f); paragraph.setSpacingAfter(2f); } else if(3 == type){ paragraph.setSpacingBefore(2f); paragraph.setSpacingAfter(1f); } else if(4 == type){ paragraph.setSpacingBefore(2f); paragraph.setSpacingAfter(2f); } else if(5 == type){ paragraph.setAlignment(Element.ALIGN_JUSTIFIED); paragraph.setFirstLineIndent(24); paragraph.setSpacingBefore(1f); paragraph.setSpacingAfter(1f); } else if(6 == type){ paragraph.setAlignment(Element.ALIGN_LEFT); paragraph.setSpacingBefore(1f); paragraph.setSpacingAfter(1f); } return paragraph; } /** * 设置自定义文本 * @param text 文本内容 * @param size 文本字号 * @param alignment 对齐方式 * @param firstLineIndex 首行是否进行缩进 * @param spacingBefore 段落上空白 * @param spacingAfter 段落下空白 * @return */ public void setParagraph(String text, Float size, int alignment, int firstLineIndex, Float spacingBefore, Float spacingAfter) { Paragraph paragraph = new Paragraph(text, defaultFont(size)); if (alignment != 0) { paragraph.setAlignment(alignment); } paragraph.setFirstLineIndent(firstLineIndex); paragraph.setSpacingBefore(spacingBefore); paragraph.setSpacingAfter(spacingAfter); addContent(paragraph); } /** * 添加 PDF 内容 * 新增工具类方法时,把想生成的内容先调用此方法 * @param element * @return 如果添加了元素,则为true ,否则为false */ public boolean addContent(Element element) { try { return this.add(element); } catch (DocumentException e) { throw new RuntimeException(e); } } //------------------------------------表格内容------------------------------------ /** * 表格内容 * @param content * @param alignmentMode * @return * @throws IOException * @throws DocumentException */ public PdfPCell createCell(String content, int alignmentMode) throws IOException, DocumentException { PdfPCell cell = new PdfPCell(); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); cell.setHorizontalAlignment(alignmentMode); cell.setPhrase(new Phrase(content, defaultFont())); return cell; } /** * 表格内既有文字又有图片 * @param content * @return * @throws IOException * @throws DocumentException */ public Phrase createTextInImageCell(String content) throws IOException, DocumentException { return new Phrase(content, defaultFont()); } /** * 表格内居中对齐 * @param content * @return * @throws IOException * @throws DocumentException */ public PdfPCell createCenterCell(String content) throws IOException, DocumentException { PdfPCell cell = createCell(content, Element.ALIGN_CENTER); return cell; } /** * 表格内左对齐 * @param content * @return * @throws IOException * @throws DocumentException */ public PdfPCell createLeftCell(String content) throws IOException, DocumentException { PdfPCell cell = createCell(content, Element.ALIGN_LEFT); return cell; } //------------------------------------字体------------------------------------ /** * 默认中文字体<br> * 大小:12<br> * 样式:正常<br> * 颜色:黑色<br> * @return 中文字体 {@link Font} */ public Font defaultFont() { return chineseFont(10.5f, Font.NORMAL, BaseColor.BLACK); } /** * 自定义字体的默认中文字体 * @param size * @return */ public Font defaultFont(Float size) { return chineseFont(size, Font.NORMAL, BaseColor.BLACK); } /** * 默认中文字体 * @return 中文字体 {@link BaseFont} */ public BaseFont defaultBaseFont() { try { return BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.EMBEDDED); } catch (DocumentException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } /** * 中文字体 {@link Font} * @param fontSize 字体大小 * @param style 字体风格 {@link Font} * @param color 字体颜色 {@link BaseColor} * @return {@link Font} */ public Font chineseFont(float fontSize, int style, BaseColor color) { BaseFont baseFont = null; try { baseFont = BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.EMBEDDED); } catch (DocumentException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return createFont(baseFont, fontSize, style, color); } /** * 自定义中文字体 {@link Font} * @param fontPath 字体格式文件路径(如:.ttc、.ttf),支持绝对路径或项目静态资源相对路径 * @param fontSize 字体大小 * @param style 字体风格 {@link Font} * @param color 字体颜色 {@link BaseColor} * @return {@link Font} */ public Font customFont(String fontPath, float fontSize, int style, BaseColor color) { BaseFont baseFont = null; try { baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); } catch (DocumentException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return createFont(baseFont, fontSize, style, color); } /** * 创建字体 {@link Font} * @param baseFont 字体 {@link BaseFont} * @param fontSize 字体大小 * @param style 字体风格 {@link Font} * @param color 字体颜色 {@link BaseColor} * @return {@link Font} */ public Font createFont(BaseFont baseFont, float fontSize, int style, BaseColor color) { return new Font(baseFont, fontSize, style, color); } /** * 字体 * @return * @throws IOException * @throws DocumentException */ public BaseFont getBaseFont() throws IOException, DocumentException { return BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.NOT_EMBEDDED); } //------------------------------------其他------------------------------------ /** * 根据标题列表计算列表列数 * @param header 标题列表 * @return 列表列数 */ private int calculateColumnNumber(List<String> header) { int numColumns = 0; for (String s : header) { numColumns += calculateColumnNumber(s); } return numColumns; } /** * 根据标题计算列表列数 * @param header 标题列表 * @return 列表列数 */ private int calculateColumnNumber(String header) { int numColumns = header.length(); if (!isChinese(header)) { numColumns = numColumns / 2 + (numColumns % 2 != 0 ? 1 : 0); } return numColumns; } /** * 字符串是否是中文 * @param str 字符串 * @return 是否是中文 */ private boolean isChinese(String str) { Pattern p = Pattern.compile("[\u4e00-\u9fa5]"); Matcher m = p.matcher(str); return m.find(); } }