Java利用IText导出PDF(更新)
我很久以前写的还是上大学的时候写的:https://www.cnblogs.com/LUA123/p/5108007.html ,今天心血来潮决定更新一波。
看了下官网(https://itextpdf.com/en),出来个IText 7,但是这个要收费的,怎么收费我也不清楚,为了避免不必要的麻烦,还是用IText5版本玩了。
正文
首先引入依赖:(目前最新版的)
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.1</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.13.1</version> </dependency>
示例一:HTML文件转PDF
web.html
<div style="text-align: center"><b><span style="font-size: large">Terms and Conditions</span></b></div> <ul> <li>Prices are in AED</li> <li>All Credit Card transactions are subject to a 3.25% processing fee</li> <li>In the event production is required per customer request, 50% of the entire bill will be due prior to start of production, and the balance due upon delivery.</li> <li>All furniture will be delivered in A+ condition. In the event that the equipment is damaged, the renter shall be liable for all repair costs to restore the equipment to its state at the beginning of the rental period.</li> <li>Equipment shall be utilized for the stated purpose and at the stated location only.</li> </ul> <ul class="chinese" style="font-family: SimSun;" > <li>价格以迪拉姆为单位</li> <li>所有信用卡交易都要支付3.25%的手续费</li> <li>如果客户要求生产,则应支付全部账单的50%</li> <li>在开始生产之前,以及交货时的余额。所有家具将以+状态交付。如果设备损坏,承租人应承担所有维修费用,以将设备恢复至租期。</li> <li>设备应仅用于规定用途和规定位置。</li> </ul>
web.css
ul li { color: #0ba79c; } .chinese li { color: #ccc920; }
代码
package com.demo.pdf; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Element; import com.itextpdf.text.Utilities; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.XMLWorker; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; import com.itextpdf.tool.xml.css.CssFile; import com.itextpdf.tool.xml.css.StyleAttrCSSResolver; import com.itextpdf.tool.xml.html.CssAppliers; import com.itextpdf.tool.xml.html.CssAppliersImpl; import com.itextpdf.tool.xml.html.Tags; import com.itextpdf.tool.xml.parser.XMLParser; import com.itextpdf.tool.xml.pipeline.css.CSSResolver; import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline; import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline; import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline; import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; /** * HTML转PDF */ public class HTMLAndPDF { public static final String PDF = "pdf/web.pdf"; public static final String PDF2 = "pdf/web2.pdf"; public static final String PDF3 = "pdf/web3.pdf"; public static final String PDF4 = "pdf/web4.pdf"; public static final String HTML = "pdf/web.html"; public static final String CSS = "pdf/web.css"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(PDF); file.getParentFile().mkdirs(); new HTMLAndPDF().createPdf(PDF); file = new File(PDF2); file.getParentFile().mkdirs(); new HTMLAndPDF().createPdf2(PDF2); file = new File(PDF3); file.getParentFile().mkdirs(); new HTMLAndPDF().createPdf3(PDF3); file = new File(PDF4); file.getParentFile().mkdirs(); new HTMLAndPDF().createPdf4(PDF4); } /** * 原封不动转换 * @param file * @throws IOException * @throws DocumentException */ public void createPdf(String file) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file)); writer.setInitialLeading(12); // step 3 document.open(); // step 4 XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(HTML), Charset.forName("UTF-8")); // step 5 document.close(); } /** * 引入额外的css * @param file * @throws IOException * @throws DocumentException */ public void createPdf2(String file) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file)); writer.setInitialLeading(12); // step 3 document.open(); // step 4 XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(HTML)); String html = Utilities.readFileToString(HTML); String css = "ul { list-style: disc } li { padding: 10px }"; PdfPTable table = new PdfPTable(1); table.setSpacingBefore(20); PdfPCell cell = new PdfPCell(); for (Element e : XMLWorkerHelper.parseToElementList(html, css)) { cell.addElement(e); } table.addCell(cell); document.add(table); // step 5 document.close(); } /** * 引入外部css * @param file * @throws IOException * @throws DocumentException */ public void createPdf3(String file) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file)); writer.setInitialLeading(12.5f); // step 3 document.open(); // step 4 // CSS CSSResolver cssResolver = new StyleAttrCSSResolver(); CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream(CSS)); cssResolver.addCss(cssFile); // HTML HtmlPipelineContext htmlContext = new HtmlPipelineContext(null); htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory()); // Pipelines PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer); HtmlPipeline html = new HtmlPipeline(htmlContext, pdf); CssResolverPipeline css = new CssResolverPipeline(cssResolver, html); // XML Worker XMLWorker worker = new XMLWorker(css, true); XMLParser p = new XMLParser(worker); p.parse(new FileInputStream(HTML)); // step 5 document.close(); } /** * 处理中文(引入外部字体文件) * @param file * @throws IOException * @throws DocumentException */ public void createPdf4(String file) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file)); writer.setInitialLeading(12.5f); // step 3 document.open(); // step 4 // CSS CSSResolver cssResolver = new StyleAttrCSSResolver(); CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream(CSS)); cssResolver.addCss(cssFile); // HTML XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS); fontProvider.register("pdf/华庚少女字体.ttf", "girl"); // 字体别名,在web.html使用 CssAppliers cssAppliers = new CssAppliersImpl(fontProvider); HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers); htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory()); // Pipelines PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer); HtmlPipeline html = new HtmlPipeline(htmlContext, pdf); CssResolverPipeline css = new CssResolverPipeline(cssResolver, html); // XML Worker XMLWorker worker = new XMLWorker(css, true); XMLParser p = new XMLParser(worker); p.parse(new FileInputStream(HTML), Charset.forName("UTF-8")); // step 5 document.close(); } }
第一个输出:
第二个输出:
第三个输出:
第四个输出:
大家可以看到中文的问题,注意点有两个:html文件指定 font-family;如果引入外部字体文件,别名要与font-family一致。文件:https://github.com/Mysakura/DataFiles
第四个要想成功,需要将web.html文件里的font-family修改
所以呢,如果你对字体没啥要求,那font-family就指定常用中文字体即可,宋体,雅黑什么的
这部分涉及的文件
注意!!!如果你外部字体为ttc文件,比如simsun.ttc,在引入的地方就要注意写法,如下:后面有个[,1]
合并PDF文件 & 嵌入外部字体
提示:如果你运行上面的例子,你可以发现我的输入输出文件都在工程根目录的pdf文件夹里。
代码
package com.demo.pdf; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; /** * 合并文档 & 嵌入字体 */ public class MergeAndAddFont { public static final String FONT = "pdf/华庚少女字体.ttf"; public static final Integer FILE_NUM = 2; // 合并两个文件 public static final String[] FILE_A = { "pdf/testA0.pdf", "pdf/testA1.pdf" }; public static final String[] FILE_B = { "pdf/testB0.pdf", "pdf/testB1.pdf" }; public static final String[] FILE_C = { "pdf/testC0.pdf", "pdf/testC1.pdf" }; public static final String[] CONTENT = { "琪亚娜·卡斯兰娜", "德丽莎·阿波卡利斯" }; public static final String MERGED_A1 = "pdf/testA_merged1.pdf"; public static final String MERGED_A2 = "pdf/testA_merged2.pdf"; public static final String MERGED_B1 = "pdf/testB_merged1.pdf"; public static final String MERGED_B2 = "pdf/testB_merged2.pdf"; public static final String MERGED_C1 = "pdf/testC_merged1.pdf"; public static final String MERGED_C2 = "pdf/testC_merged2.pdf"; public static void main(String[] args) throws DocumentException, IOException { File file = new File(MERGED_A1); file.getParentFile().mkdirs(); MergeAndAddFont app = new MergeAndAddFont(); // 测试一:嵌入字体;生成的文件仅仅包含用到的字形;智能合并;非智能合并 for (int i = 0; i < FILE_A.length; i++) { app.createPdf(FILE_A[i], CONTENT[i], true, true); } app.mergeFiles(FILE_A, MERGED_A1,false); app.mergeFiles(FILE_A, MERGED_A2, true); // 测试二:嵌入字体;生成的文件包含完整字体;智能合并;非智能合并 for (int i = 0; i < FILE_B.length; i++) { app.createPdf(FILE_B[i], CONTENT[i], true, false); } app.mergeFiles(FILE_B, MERGED_B1,false); app.mergeFiles(FILE_B, MERGED_B2, true); // 测试三:不嵌入字体;生成的文件包含完整字体;智能合并;手动嵌入字体 for (int i = 0; i < FILE_C.length; i++) { app.createPdf(FILE_C[i], CONTENT[i], false, false); } app.mergeFiles(FILE_C, MERGED_C1, true); app.embedFont(MERGED_C1, FONT, MERGED_C2); } /** * * @param filename * @param text * @param embedded true在PDF中嵌入字体,false不嵌入 * @param subset true仅仅包含用到的字形,false包含完整字体 * @throws DocumentException * @throws IOException */ public void createPdf(String filename, String text, boolean embedded, boolean subset) throws DocumentException, IOException { // step 1 Document document = new Document(); // step 2 PdfWriter.getInstance(document, new FileOutputStream(filename)); // step 3 document.open(); // step 4 BaseFont bf = BaseFont.createFont(FONT, BaseFont.IDENTITY_H, embedded); // 生成文件大小与编码有关,如果你没有中文,那么编码用BaseFont.WINANSI就节约很多资源了。 bf.setSubset(subset); Font font = new Font(bf, 12); document.add(new Paragraph(text, font)); // step 5 document.close(); } /** * 合并文件 * @param files * @param result * @param smart 智能Copy * @throws IOException * @throws DocumentException */ public void mergeFiles(String[] files, String result, boolean smart) throws IOException, DocumentException { Document document = new Document(); PdfCopy copy; if (smart) copy = new PdfSmartCopy(document, new FileOutputStream(result)); else copy = new PdfCopy(document, new FileOutputStream(result)); document.open(); PdfReader[] reader = new PdfReader[FILE_NUM]; for (int i = 0; i < files.length; i++) { reader[i] = new PdfReader(files[i]); copy.addDocument(reader[i]); copy.freeReader(reader[i]); reader[i].close(); } document.close(); } /** * 嵌入字体 * @param merged * @param fontfile * @param result * @throws IOException * @throws DocumentException */ private void embedFont(String merged, String fontfile, String result) throws IOException, DocumentException { // the font file RandomAccessFile raf = new RandomAccessFile(fontfile, "r"); byte fontbytes[] = new byte[(int)raf.length()]; raf.readFully(fontbytes); raf.close(); // create a new stream for the font file PdfStream stream = new PdfStream(fontbytes); stream.flateCompress(); stream.put(PdfName.LENGTH1, new PdfNumber(fontbytes.length)); // create a reader object PdfReader reader = new PdfReader(merged); int n = reader.getXrefSize(); PdfObject object; PdfDictionary font; PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(result)); PdfName fontname = new PdfName(BaseFont.createFont(fontfile, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getPostscriptFontName()); for (int i = 0; i < n; i++) { object = reader.getPdfObject(i); if (object == null || !object.isDictionary()) continue; font = (PdfDictionary)object; if (PdfName.FONTDESCRIPTOR.equals(font.get(PdfName.TYPE)) && fontname.equals(font.get(PdfName.FONTNAME))) { PdfIndirectObject objref = stamper.getWriter().addToBody(stream); font.put(PdfName.FONTFILE2, objref.getIndirectReference()); } } stamper.close(); reader.close(); } }
运行之后会生成12个文件。
直观一点的(看文件体积)
首先看A系列,因为它在创建文件的时候就指定包含用到的字形,所以独立文件的文件属性都是
合并文件都是
再来看B系列,因为它指定包含完整字体,所以体积很大。不同的是,合并1是非智能的,所以体积是智能的2倍。独立文件和合并文件的文件属性都是(已嵌入)
最后看C系列【这里中英文的出入比较大】,如果你是中文PDF,那么文档属性都是已嵌入并且手动嵌入的体积是其它的2倍。
如果你是英文文档,代码如下,只需要改动两处(1. 输入英文,中文不显示 2. 更改字体编码),生成的文件C系列大不一样。

package com.demo.pdf; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; /** * 合并文档 & 嵌入字体 */ public class MergeAndAddFont3 { public static final String FONT = "pdf/华庚少女字体.ttf"; public static final Integer FILE_NUM = 2; // 合并两个文件 public static final String[] FILE_A = { "pdf/en/testA0.pdf", "pdf/en/testA1.pdf" }; public static final String[] FILE_B = { "pdf/en/testB0.pdf", "pdf/en/testB1.pdf" }; public static final String[] FILE_C = { "pdf/en/testC0.pdf", "pdf/en/testC1.pdf" }; // 英文PDF内容 public static final String[] CONTENT = { "ABCD", "EFGK" }; public static final String MERGED_A1 = "pdf/en/testA_merged1.pdf"; public static final String MERGED_A2 = "pdf/en/testA_merged2.pdf"; public static final String MERGED_B1 = "pdf/en/testB_merged1.pdf"; public static final String MERGED_B2 = "pdf/en/testB_merged2.pdf"; public static final String MERGED_C1 = "pdf/en/testC_merged1.pdf"; public static final String MERGED_C2 = "pdf/en/testC_merged2.pdf"; public static void main(String[] args) throws DocumentException, IOException { File file = new File(MERGED_A1); file.getParentFile().mkdirs(); MergeAndAddFont3 app = new MergeAndAddFont3(); // 测试一:嵌入字体;生成的文件仅仅包含用到的字形;智能合并;非智能合并 for (int i = 0; i < FILE_A.length; i++) { app.createPdf(FILE_A[i], CONTENT[i], true, true); } app.mergeFiles(FILE_A, MERGED_A1,false); app.mergeFiles(FILE_A, MERGED_A2, true); // 测试二:嵌入字体;生成的文件包含完整字体;智能合并;非智能合并 for (int i = 0; i < FILE_B.length; i++) { app.createPdf(FILE_B[i], CONTENT[i], true, false); } app.mergeFiles(FILE_B, MERGED_B1,false); app.mergeFiles(FILE_B, MERGED_B2, true); // 测试三:不嵌入字体;生成的文件包含完整字体;智能合并;手动嵌入字体 for (int i = 0; i < FILE_C.length; i++) { app.createPdf(FILE_C[i], CONTENT[i], false, false); } app.mergeFiles(FILE_C, MERGED_C1, true); app.embedFont(MERGED_C1, FONT, MERGED_C2); } /** * * @param filename * @param text * @param embedded true在PDF中嵌入字体,false不嵌入 * @param subset true仅仅包含用到的字形,false包含完整字体 * @throws DocumentException * @throws IOException */ public void createPdf(String filename, String text, boolean embedded, boolean subset) throws DocumentException, IOException { // step 1 Document document = new Document(); // step 2 PdfWriter.getInstance(document, new FileOutputStream(filename)); // step 3 document.open(); // 英文编码 BaseFont bf = BaseFont.createFont(FONT, BaseFont.WINANSI, embedded); // 生成文件大小与编码有关,如果你没有中文,那么编码用BaseFont.WINANSI就节约很多资源了。 bf.setSubset(subset); Font font = new Font(bf, 12); document.add(new Paragraph(text, font)); // step 5 document.close(); } /** * 合并文件 * @param files * @param result * @param smart 智能Copy * @throws IOException * @throws DocumentException */ public void mergeFiles(String[] files, String result, boolean smart) throws IOException, DocumentException { Document document = new Document(); PdfCopy copy; if (smart) copy = new PdfSmartCopy(document, new FileOutputStream(result)); else copy = new PdfCopy(document, new FileOutputStream(result)); document.open(); PdfReader[] reader = new PdfReader[FILE_NUM]; for (int i = 0; i < files.length; i++) { reader[i] = new PdfReader(files[i]); copy.addDocument(reader[i]); copy.freeReader(reader[i]); reader[i].close(); } document.close(); } /** * 嵌入字体 * @param merged * @param fontfile * @param result * @throws IOException * @throws DocumentException */ private void embedFont(String merged, String fontfile, String result) throws IOException, DocumentException { // the font file RandomAccessFile raf = new RandomAccessFile(fontfile, "r"); byte fontbytes[] = new byte[(int)raf.length()]; raf.readFully(fontbytes); raf.close(); // create a new stream for the font file PdfStream stream = new PdfStream(fontbytes); stream.flateCompress(); stream.put(PdfName.LENGTH1, new PdfNumber(fontbytes.length)); // create a reader object PdfReader reader = new PdfReader(merged); int n = reader.getXrefSize(); PdfObject object; PdfDictionary font; PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(result)); PdfName fontname = new PdfName(BaseFont.createFont(fontfile, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getPostscriptFontName()); for (int i = 0; i < n; i++) { object = reader.getPdfObject(i); if (object == null || !object.isDictionary()) continue; font = (PdfDictionary)object; if (PdfName.FONTDESCRIPTOR.equals(font.get(PdfName.TYPE)) && fontname.equals(font.get(PdfName.FONTNAME))) { PdfIndirectObject objref = stamper.getWriter().addToBody(stream); font.put(PdfName.FONTFILE2, objref.getIndirectReference()); } } stamper.close(); reader.close(); } }
在英文C系列,独立文件和智能合并的文件属性都是
而手动嵌入字体的则是
所以得出结论:
A系列相当于正常使用,基本都是默认值。对于嵌入字体需求的:
无论你是中文文档还是英文文档,就选择B系列,因为无论生成的独立文件还是合并文件都已嵌入字体。并且大小已是极限了。
C系列的话,注意除了手动嵌入方法,其它情况都不嵌入的。这意味着什么呢?【已嵌入子集】【已嵌入】,则表示你的字体不会发生变化。如果不带这个,就会造成:如果你换了个没有这种字体的系统,字体会显示为当前系统的默认字体。如下是我打开的
中文系列则应该是
这仅仅对于有嵌入字体需求的,正常输出PDF的话不用考虑这么多,因为默认subset=true,PDF仅仅包含用到的字形。
还有一点,重复试验之后,发现只有你已使用的字体,才可以嵌入。PDF中未用到的字体是无法嵌入的。
package com.demo.pdf; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; /** * 合并文档 & 嵌入字体 */ public class MergeAndAddFont2 { public static final String FONT = "pdf/华庚少女字体.ttf"; public static final String FONT_FILE = "pdf/simsun.ttc"; public static final String SRC_PDF = "pdf/testD0.pdf"; public static void main(String[] args) throws DocumentException, IOException { File file = new File(SRC_PDF); file.getParentFile().mkdirs(); MergeAndAddFont2 app = new MergeAndAddFont2(); app.createPdf(SRC_PDF, "琪亚娜·卡斯兰娜", true, false); app.embedFont(SRC_PDF, FONT_FILE, "pdf/testD_embedded.pdf"); } public void createPdf(String filename, String text, boolean embedded, boolean subset) throws DocumentException, IOException { // step 1 Document document = new Document(); // step 2 PdfWriter.getInstance(document, new FileOutputStream(filename)); // step 3 document.open(); // step 4 BaseFont bf = BaseFont.createFont(FONT, BaseFont.IDENTITY_H, embedded); bf.setSubset(subset); Font font = new Font(bf, 12); document.add(new Paragraph(text, font)); // step 5 document.close(); } /** * 嵌入字体 * @param src * @param fontfile ttc文件地址(pdf/simsun.ttc) * @param result * @throws IOException * @throws DocumentException */ private void embedFont(String src, String fontfile, String result) throws IOException, DocumentException { // 读取字体文件 RandomAccessFile raf = new RandomAccessFile(fontfile, "r"); byte fontbytes[] = new byte[(int)raf.length()]; raf.readFully(fontbytes); raf.close(); // 创建一个字体文件流 PdfStream stream = new PdfStream(fontbytes); stream.flateCompress(); stream.put(PdfName.LENGTH1, new PdfNumber(fontbytes.length)); // 读取已有的PDF PdfReader reader = new PdfReader(src); int n = reader.getXrefSize(); PdfObject object; PdfDictionary font; // 添加额外的内容到已有的PDF中 PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(result)); // 这里的字体文件应该是PDF中已有的字体【正常来讲,这里声明的字体应该和待嵌入的一致】 PdfName fontname = new PdfName(BaseFont.createFont(FONT, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getPostscriptFontName()); // 遍历源文件,找到 /FontDescriptor,以及对应的字体。嵌入字体 for (int i = 0; i < n; i++) { object = reader.getPdfObject(i); // 为空或者不是目录,跳过 if (object == null || !object.isDictionary()) continue; font = (PdfDictionary)object; // 目录类型和名称 System.out.println(font.get(PdfName.TYPE) + "\t" + font.get(PdfName.FONTNAME)); if (PdfName.FONTDESCRIPTOR.equals(font.get(PdfName.TYPE)) && fontname.equals(font.get(PdfName.FONTNAME))) { PdfIndirectObject objref = stamper.getWriter().addToBody(stream); font.put(PdfName.FONTFILE2, objref.getIndirectReference()); } } stamper.close(); reader.close(); } }
打印输出:
可以看到,源文件只有一种字体,就是 少女字体。而我们也成功的把宋体文件写进入了,至少表面上看是这样的。如下:
我们打开源文件
再来看我们期望的PDF文件,到底宋体进没进去呢。
可以看到,虽然PDF体积增加了,但是字体文件并没有进去,还造成了乱码。所以,手动嵌入字体仅仅针对那些,已使用了某个字体,但是没有嵌入的情况。
BaseFont bf = BaseFont.createFont(FONT, BaseFont.IDENTITY_H, embedded); // 正常使用的话,embedded为true就能满足日常需求,subset使用默认值即可,不需设置。
条形码
package com.demo.pdf; import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * 条形码 */ public class SmallTable { public static final String FONT = "pdf/华庚少女字体.ttf"; public static final String DEST = "pdf/small_table.pdf"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(DEST); file.getParentFile().mkdirs(); new SmallTable().createPdf(DEST); } public void createPdf(String dest) throws IOException, DocumentException { Rectangle small = new Rectangle(290,100); BaseFont bf = BaseFont.createFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); Font font = new Font(bf, 12); Document document = new Document(small, 5, 5, 5, 5); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest)); document.open(); PdfPTable table = new PdfPTable(2); table.setTotalWidth(new float[]{ 160, 120 }); table.setLockedWidth(true); PdfContentByte cb = writer.getDirectContent(); // first row PdfPCell cell = new PdfPCell(new Phrase("条形码", font)); cell.setFixedHeight(30); cell.setBorder(Rectangle.NO_BORDER); cell.setColspan(2); cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); table.addCell(cell); // second row Barcode128 code128 = new Barcode128(); code128.setCode("14785236987541"); code128.setCodeType(Barcode128.CODE128); Image code128Image = code128.createImageWithBarcode(cb, null, null); cell = new PdfPCell(code128Image, true); cell.setColspan(2); cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); cell.setBorder(Rectangle.NO_BORDER); cell.setFixedHeight(30); table.addCell(cell); document.add(table); document.close(); } }
输出:
单元格以图片为背景
package com.demo.pdf; import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * 单元格图片背景 */ public class ImageBackground { class ImageBackgroundEvent implements PdfPCellEvent { protected Image image; public ImageBackgroundEvent(Image image) { this.image = image; } public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) { try { PdfContentByte cb = canvases[PdfPTable.BACKGROUNDCANVAS]; image.scaleAbsolute(position); image.setAbsolutePosition(position.getLeft(), position.getBottom()); cb.addImage(image); } catch (DocumentException e) { throw new ExceptionConverter(e); } } } public static final String DEST = "pdf/image_background.pdf"; public static final String IMG1 = "pdf/bruno.jpg"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(DEST); file.getParentFile().mkdirs(); new ImageBackground().createPdf(DEST); } public void createPdf(String dest) throws IOException, DocumentException { Document document = new Document(); PdfWriter.getInstance(document, new FileOutputStream(dest)); document.open(); PdfPTable table = new PdfPTable(1); table.setTotalWidth(400); table.setLockedWidth(true); PdfPCell cell = new PdfPCell(); Font font = new Font(Font.FontFamily.HELVETICA, 12, Font.NORMAL, GrayColor.GRAYWHITE); Paragraph p = new Paragraph("A cell with an image as background.", font); cell.addElement(p); Image image = Image.getInstance(IMG1); cell.setCellEvent(new ImageBackgroundEvent(image)); cell.setFixedHeight(600 * image.getScaledHeight() / image.getScaledWidth()); table.addCell(cell); document.add(table); document.close(); } }
输出:
小示例(本人不才,简单画了个表格)
package com.demo.pdf; 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.File; import java.io.FileOutputStream; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * 一些示例 */ public class Tables { public static final String FONT = "pdf/华庚少女字体.ttf"; public static final String DEST = "pdf/tables.pdf"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(DEST); file.getParentFile().mkdirs(); new Tables().createPdf(DEST); } public void createPdf(String dest) throws IOException, DocumentException { Document document = new Document(); PdfWriter.getInstance(document, new FileOutputStream(dest)); document.open(); // 使用语言包字体 BaseFont abf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); // 外部字体 BaseFont bf = BaseFont.createFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); Font titleFont = new Font(bf, 12, Font.BOLD); titleFont.setColor(new BaseColor(76,175,80)); Font font = new Font(bf, 10); Paragraph p = new Paragraph("个人简历表", new Font(abf, 12, Font.BOLD)); p.setAlignment(Paragraph.ALIGN_CENTER); document.add(p); PdfPTable table = new PdfPTable(8); table.setSpacingBefore(16f); PdfPCell cell = new PdfPCell(new Phrase("姓名", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("德丽莎", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("性别", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("女", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("出生年月", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("3月28日", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("民族", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("天命→休伯利安", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("个人简历", titleFont)); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); table.addCell(cell); cell = new PdfPCell(); cell.setColspan(7); cell.addElement(new Paragraph("卡斯兰娜家族一贯的白发蓝眸,长发扎成马尾披于左肩。常穿天命早期使用的女武神统一制服。德丽莎所穿的制服是特别定制款,由主教奥托亲手缝制,在衣服胸口处别着阿波卡利斯家族的家徽。", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("家庭成员", titleFont)); cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); cell.setRowspan(3); table.addCell(cell); cell = new PdfPCell(new Phrase("称呼", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("姓名", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("关系", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(5); table.addCell(cell); cell = new PdfPCell(new Phrase("爷爷", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("奥托·阿波卡利斯", font)); table.addCell(cell); cell = new PdfPCell(); cell.addElement(new Paragraph("德丽莎的造物主,奥托赐予了德丽莎名字,认其为孙女。因为德丽莎温柔而又坚强的性格,让奥托多次产生德丽莎是卡莲的转世的想法", font)); cell.setColspan(5); table.addCell(cell); cell = new PdfPCell(new Phrase("侄女", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("琪亚娜·卡斯兰娜", font)); table.addCell(cell); cell = new PdfPCell(); cell.addElement(new Paragraph("塞西莉亚和齐格飞的女儿,两人让德丽莎当琪亚娜的教母,琪亚娜这个名字也是德丽莎起的。齐格飞逃离天命的行动中,本想一起逃离天命的德丽莎为保护备天命回收的琪亚娜,回到天命。", font)); cell.setColspan(5); table.addCell(cell); cell = new PdfPCell(new Phrase("他人评价", titleFont)); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); table.addCell(cell); cell = new PdfPCell(); cell.setColspan(7); cell.setPaddingLeft(8f); cell.setPaddingRight(8f); cell.setPaddingBottom(16f); // 配置行间距 cell.addElement(new Paragraph(24, "“即使离开了天命,您也依然是我们所尊敬的学园长。”——雷电芽衣\n" + "“虽然看起来很小,倒也有点本事。”——西琳 \n" + "“诶~德丽莎看起来小小的,意外地很能干嘛。”——萝莎莉娅·阿琳", font)); table.addCell(cell); cell = new PdfPCell(new Phrase("其它", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); table.addCell(cell); cell = new PdfPCell(new Phrase("···")); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); cell.setFixedHeight(32f); cell.setColspan(7); table.addCell(cell); document.add(table); // ------------------------------------------------ table = new PdfPTable(4); table.setSpacingBefore(32f); Font titleFont2 = new Font(bf, 14, Font.BOLD); titleFont2.setColor(new BaseColor(255,255,255)); cell = new PdfPCell(new Phrase("账单", titleFont2)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); cell.setColspan(4); cell.setFixedHeight(32f); cell.setBackgroundColor(new BaseColor(96, 125, 139)); cell.setBorder(Rectangle.NO_BORDER); table.addCell(cell); cell = new PdfPCell(new Phrase("日期", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("项目", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("金额", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); cell = new PdfPCell(new Phrase("说明", titleFont)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); LocalDateTime dt = LocalDateTime.now(); for (int i = 0; i < 10; i++){ dt = dt.plusDays(1L); cell = new PdfPCell(new Phrase(dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("花呗还款", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("$100.0", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("——", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); } document.add(table); // ------------------------------------------------ document.newPage(); document.add(new Paragraph("芒种\n\n" + "一想到你我就\n" + "wu~~~~\n" + "空恨别梦久\n" + "wu~~~~\n" + "烧去纸灰埋烟柳\n" + "\n" + "于鲜活的枝丫\n" + "凋零下的无暇\n" + "是收获谜底的代价\n" + "\n" + "余晖沾上 远行人的发\n" + "他洒下手中牵挂\n" + "于桥下\n" + "前世迟来者~~~(擦肩而过)\n" + "掌心刻~~~~~(来生记得)\n" + "你眼中烟波滴落一滴墨 wo~~~\n" + "若佛说~~~~~(无牵无挂)\n" + "放下执着~~~~~(无相无色)\n" + "我怎能 波澜不惊 去附和\n" + "\n" + "一想到你我就\n" + "wu~~~~~\n" + "恨情不寿 总于苦海囚\n" + "wu~~~~~\n" + "新翠徒留 落花影中游\n" + "wu~~~~~\n" + "相思无用 才笑山盟旧\n" + "wu~~~~~\n" + "谓我何求\n" + "\n" + "种一万朵莲花\n" + "在众生中发芽\n" + "等红尘一万种解答\n" + "\n" + "念珠落进 时间的泥沙\n" + "待 割舍诠释慈悲\n" + "的读法\n" + "\n" + "前世迟来者~~~(擦肩而过)\n" + "掌心刻~~~~~(来生记得)\n" + "你眼中烟波滴落一滴墨 wo~~~\n" + "若佛说~~~~~(无牵无挂)\n" + "放下执着~~~~~(无相无色)\n" + "我怎能 波澜不惊 去附和", font)); document.close(); } }
输出
页眉页脚
package com.demo.pdf; import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Random; /** * 页眉页脚 */ public class PageNumber { class Header extends PdfPageEventHelper { @Override public void onStartPage(PdfWriter writer, Document document) { PdfPTable table = new PdfPTable(1); try { Font font = new Font(BaseFont.createFont(FONT, BaseFont.IDENTITY_H, BaseFont.EMBEDDED), 10); table.setTotalWidth(PageSize.A4.getWidth()); // A4大小 table.getDefaultCell().setFixedHeight(20); PdfPCell cell = new PdfPCell(new Phrase("XXX生活支出明细", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setBorder(Rectangle.BOTTOM); cell.setPaddingBottom(6f); table.addCell(cell); PdfContentByte canvas = writer.getDirectContent(); table.writeSelectedRows(0, -1, ((document.right() + document.rightMargin())-PageSize.A4.getWidth())/2, document.top()+25, canvas); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Override public void onEndPage(PdfWriter writer, Document document) { PdfPTable table = new PdfPTable(1); try { Font font = new Font(Font.FontFamily.SYMBOL, 10); table.setTotalWidth(PageSize.A4.getWidth()); // A4大小 table.getDefaultCell().setFixedHeight(20); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); table.getDefaultCell().setBorder(Rectangle.NO_BORDER); table.addCell(new Phrase(writer.getPageNumber()+"", font)); PdfContentByte canvas = writer.getDirectContent(); table.writeSelectedRows(0, -1, ((document.right() + document.rightMargin())-PageSize.A4.getWidth())/2, document.bottom(), canvas); } catch (Exception e) { e.printStackTrace(); } } } public static final String DEST = "pdf/pdf_with_head_foot.pdf"; public static final String FONT = "pdf/华庚少女字体.ttf"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(DEST); file.getParentFile().mkdirs(); new PageNumber().createPdf(DEST); } public void createPdf(String dest) throws IOException, DocumentException { Font font = new Font(BaseFont.createFont(FONT, BaseFont.IDENTITY_H, BaseFont.EMBEDDED), 10); Document document = new Document(PageSize.A4.rotate()); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest)); writer.setPageEvent(new Header()); document.open(); PdfDictionary parameters = new PdfDictionary(); parameters.put(PdfName.MODDATE, new PdfDate()); PdfPTable table = new PdfPTable(3); table.setTotalWidth(PageSize.A4.getWidth()); // 锁定宽度 table.setLockedWidth(true); table.setWidths(new int[]{4, 4, 2}); PdfPCell cell = new PdfPCell(new Phrase("去向", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("金额", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("备注", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); Random random = new Random(); for (int i = 0; i < 30; i++){ cell = new PdfPCell(new Phrase("花呗还款", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("$" + random.nextInt(500), font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); cell = new PdfPCell(new Phrase("——", font)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPadding(8f); table.addCell(cell); } document.add(table); document.close(); } }
输出:
每一页具有相同的标题行或者页脚行
package com.demo.pdf; import com.itextpdf.text.*; import com.itextpdf.text.pdf.GrayColor; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * 跨页表格,每一页都有相同的页脚页眉行。 */ public class HeaderRowsTable { public static final String DEST = "pdf/header_row_table.pdf"; public static void main(String[] args) throws IOException, DocumentException { File file = new File(DEST); file.getParentFile().mkdirs(); new HeaderRowsTable().createPdf(DEST); } public void createPdf(String dest) throws IOException, DocumentException { Document document = new Document(); PdfWriter.getInstance(document, new FileOutputStream(dest)); document.open(); Font f = new Font(Font.FontFamily.HELVETICA, 13, Font.NORMAL, BaseColor.YELLOW); float[] columnWidths = {1, 5, 5}; PdfPTable table = new PdfPTable(columnWidths); table.setWidthPercentage(100); table.getDefaultCell().setUseAscender(true); table.getDefaultCell().setUseDescender(true); PdfPCell cell = new PdfPCell(new Phrase("Header Row And Footer Row", f)); cell.setBackgroundColor(GrayColor.GRAYBLACK); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(3); table.addCell(cell); table.getDefaultCell().setBackgroundColor(new GrayColor(0.75f)); // 每个页面出现的页眉+页脚行数 for (int i = 0; i < 2; i++) { table.addCell("#"); table.addCell("Key"); table.addCell("Value"); } // 设置标题行数。只有将表添加到文档并且表跨页时,此配置才有意义。 table.setHeaderRows(3); // 设置页脚行数。页眉行数 = 标题行数 - 页脚行数。比如此例子:每一页表格都有两行页眉行(3-1=2)和一行页脚行。 table.setFooterRows(1); table.getDefaultCell().setBackgroundColor(GrayColor.GRAYWHITE); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); for (int counter = 1; counter < 51; counter++) { table.addCell(String.valueOf(counter)); table.addCell("—— "); table.addCell("—— "); } document.add(table); document.newPage(); table = new PdfPTable(columnWidths); table.setSpacingBefore(32f); table.setWidthPercentage(100); table.getDefaultCell().setUseAscender(true); table.getDefaultCell().setUseDescender(true); cell = new PdfPCell(new Phrase("Only Have Header Row", f)); cell.setBackgroundColor(GrayColor.GRAYBLACK); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(3); table.addCell(cell); table.getDefaultCell().setBackgroundColor(new GrayColor(0.75f)); // 每个页面出现的页眉+页脚行数 for (int i = 0; i < 1; i++) { table.addCell("#"); table.addCell("Key"); table.addCell("Value"); } // 也可以仅仅设置页眉行 table.setHeaderRows(2); table.getDefaultCell().setBackgroundColor(GrayColor.GRAYWHITE); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); for (int counter = 1; counter < 51; counter++) { table.addCell(String.valueOf(counter)); table.addCell("—— "); table.addCell("—— "); } document.add(table); document.newPage(); table = new PdfPTable(columnWidths); table.setSpacingBefore(32f); table.setWidthPercentage(100); table.getDefaultCell().setUseAscender(true); table.getDefaultCell().setUseDescender(true); table.getDefaultCell().setBackgroundColor(new BaseColor(133,148,6)); // 每个页面出现的页眉+页脚行数(数量与setHeaderRows(2)一致。为什么前面示例都是少1呢?因为前面示例已经单独添加了一行主标题) for (int i = 0; i < 2; i++) { table.addCell(new Phrase("#", f)); table.addCell(new Phrase("Key", f)); table.addCell(new Phrase("Value", f)); } table.setHeaderRows(2); table.setFooterRows(1); table.getDefaultCell().setBackgroundColor(GrayColor.GRAYWHITE); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); for (int counter = 1; counter < 51; counter++) { table.addCell(String.valueOf(counter)); table.addCell("—— "); table.addCell("—— "); } document.add(table); document.close(); } }
结果:(一共三种示例,自行去比较)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)