问题解决:com.lowagie.text.DocumentException: Font 'STSong-Light' with 'UniGB-UCS2-H'

本文对比分析了iText与iTextAsian在处理中文字体加载时的区别,主要聚焦于BaseFont类的实现差异,解释了为何iTextAsian能正确识别并加载中文字体,而iText却无法做到。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正确jar包引入:(这里的版本号无所谓,下同)

<dependency>
	<groupId>com.itextpdf</groupId>
	<artifactId>itext-asian</artifactId>
	<version>5.2.0</version>
</dependency>
<dependency>
	<groupId>com.itextpdf</groupId>
	<artifactId>itextpdf</artifactId>
	<version>5.4.3</version>
</dependency>

不要引入的jar

<dependency>
	<groupId>com.lowagie</groupId>
	<artifactId>itext</artifactId>
	<version>2.1.7</version>
</dependency>

关键点在于:使用com.lowagie.text.pdf.BaseFont还是com.itextpdf.text.pdf.BaseFont

BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);

com.lowagie.text.pdf.BaseFont

这里我们先看一下com.lowagie.text.pdf.BaseFont的源码,为何会报错:Font ‘STSong-Light’ with ‘UniGB-UCS2-H’

public static BaseFont createFont(String var0, String var1, boolean var2) throws DocumentException, IOException {
    return createFont(var0, var1, var2, true, (byte[])null, (byte[])null, false);
}

public static BaseFont createFont(String var0, String var1, boolean var2, boolean var3) throws DocumentException, IOException {
    return createFont(var0, var1, var2, true, (byte[])null, (byte[])null, var3);
}

public static BaseFont createFont(String var0, String var1, boolean var2, boolean var3, byte[] var4, byte[] var5) throws DocumentException, IOException {
    return createFont(var0, var1, var2, var3, var4, var5, false);
}

public static BaseFont createFont(String var0, String var1, boolean var2, boolean var3, byte[] var4, byte[] var5, boolean var6) throws DocumentException, IOException {
    return createFont(var0, var1, var2, var3, var4, var5, false, false);
}

public static BaseFont createFont(String var0, String var1, boolean var2, boolean var3, byte[] var4, byte[] var5, boolean var6, boolean var7) throws DocumentException, IOException {
    String var8 = getBaseName(var0);
    var1 = normalizeEncoding(var1);
    //var9=false,详解如下
    boolean var9 = BuiltinFonts14.containsKey(var0);//看这里
    //var10=false,详解如下
    boolean var10 = var9 ? false : CJKFont.isCJKFont(var8, var1);//看这里
    if (!var9 && !var10) {
        if (var1.equals("Identity-H") || var1.equals("Identity-V")) {
            var2 = true;
        }
    } else {
        var2 = false;
    }
    ....
    //
    if (!var9 && !var0.toLowerCase().endsWith(".afm") && !var0.toLowerCase().endsWith(".pfm")) {
            if (!var8.toLowerCase().endsWith(".ttf") && !var8.toLowerCase().endsWith(".otf") && var8.toLowerCase().indexOf(".ttc,") <= 0) {
                if (!var10) {
                    if (var6) {
                        return null;
                    }
					//进入到这里直接跑异常
                    throw new DocumentException("Font '" + var0 + "' with '" + var1 + "' is not recognized.");
                }
    ....
}

这里我们注意下BuiltinFonts14.containsKey(var0);

protected static final HashMap BuiltinFonts14 = new HashMap();

static {
    BuiltinFonts14.put("Courier", PdfName.COURIER);
    BuiltinFonts14.put("Courier-Bold", PdfName.COURIER_BOLD);
    BuiltinFonts14.put("Courier-BoldOblique", PdfName.COURIER_BOLDOBLIQUE);
    BuiltinFonts14.put("Courier-Oblique", PdfName.COURIER_OBLIQUE);
    BuiltinFonts14.put("Helvetica", PdfName.HELVETICA);
    BuiltinFonts14.put("Helvetica-Bold", PdfName.HELVETICA_BOLD);
    BuiltinFonts14.put("Helvetica-BoldOblique", PdfName.HELVETICA_BOLDOBLIQUE);
    BuiltinFonts14.put("Helvetica-Oblique", PdfName.HELVETICA_OBLIQUE);
    BuiltinFonts14.put("Symbol", PdfName.SYMBOL);
    BuiltinFonts14.put("Times-Roman", PdfName.TIMES_ROMAN);
    BuiltinFonts14.put("Times-Bold", PdfName.TIMES_BOLD);
    BuiltinFonts14.put("Times-BoldItalic", PdfName.TIMES_BOLDITALIC);
    BuiltinFonts14.put("Times-Italic", PdfName.TIMES_ITALIC);
    BuiltinFonts14.put("ZapfDingbats", PdfName.ZAPFDINGBATS);
}

所以BuiltinFonts14.containsKey(“STSong-Light”)返回false
注意CJKFont.isCJKFont(var8, var1)这个方法;我们进CJKFont看一下

public static boolean isCJKFont(String var0, String var1) {
    loadProperties();//看这里
    String var2 = cjkFonts.getProperty(var0);
    return var2 != null && (var1.equals("Identity-H") || var1.equals("Identity-V") || var2.indexOf("_" + var1 + "_") >= 0);
}
private static void loadProperties() {
    if (!propertiesLoaded) {
        Hashtable var0 = allFonts;
        synchronized(allFonts) {
            if (!propertiesLoaded) {
                try {
                    InputStream var1 = getResourceStream("com/lowagie/text/pdf/fonts/cjkfonts.properties");
                    cjkFonts.load(var1);
                    var1.close();
                    var1 = getResourceStream("com/lowagie/text/pdf/fonts/cjkencodings.properties");
                    cjkEncodings.load(var1);
                    var1.close();
                } catch (Exception var3) {
                    cjkFonts = new Properties();
                    cjkEncodings = new Properties();
                }

                propertiesLoaded = true;
            }
        }
    }
}

CJKFont会先加载com/lowagie/text/pdf/fonts下的并不存在的cjkfonts.properties和cjkencodings.properties文件,然后再获取var0(即"STSong-Light")的值为null,所以isCJKFont方法返回false
再回上面查看代码可知,直接抛出异常

com.itextpdf.text.pdf.BaseFont

下面我们看一下com.itextpdf.text.pdf.BaseFont的源码

public static BaseFont createFont(String name, String encoding, boolean embedded, boolean cached, byte[] ttfAfm, byte[] pfb, boolean noThrow, boolean forceRead) throws DocumentException, IOException {
	String nameBase = getBaseName(name);//nameBase值为"S"
	encoding = normalizeEncoding(encoding);
	//isBuiltinFonts14=false
	boolean isBuiltinFonts14 = BuiltinFonts14.containsKey(name);//看这里
	//isCJKFont=true
	boolean isCJKFont = isBuiltinFonts14 ? false : CJKFont.isCJKFont(nameBase, encoding);//看这里
	if (!isBuiltinFonts14 && !isCJKFont) {
	    if (encoding.equals("Identity-H") || encoding.equals("Identity-V")) {
	        embedded = true;
	    }
	} else {
	    embedded = false;
	}
	....
	if (!isBuiltinFonts14 && !name.toLowerCase().endsWith(".afm") && !name.toLowerCase().endsWith(".pfm")) {
        if (!nameBase.toLowerCase().endsWith(".ttf") && !nameBase.toLowerCase().endsWith(".otf") && nameBase.toLowerCase().indexOf(".ttc,") <= 0) {
            if (!isCJKFont) {//isCJKFont=true
                if (noThrow) {
                    return null;
                }
                throw new DocumentException(MessageLocalization.getComposedMessage("font.1.with.2.is.not.recognized", new Object[]{name, encoding}));
            }
            fontBuilt = new CJKFont(name, encoding, embedded);//返回结果值
            ....
    ....
    return (BaseFont)fontBuilt;
}

BuiltinFonts14.containsKey(name);与com.lowagie.text.pdf.BaseFont中逻辑相同,这里不再讲述了。

public static boolean isCJKFont(String fontName, String enc) {
    loadProperties();//看这里
    if (!registryNames.containsKey("fonts")) {
        return false;
    } else if (!((Set)registryNames.get("fonts")).contains(fontName)) {
        return false;
    } else if (!enc.equals("Identity-H") && !enc.equals("Identity-V")) {
        String registry = (String)((HashMap)allFonts.get(fontName)).get("Registry");
        Set<String> encodings = (Set)registryNames.get(registry);
        return encodings != null && encodings.contains(enc);
    } else {
        return true;
    }
}

private static void loadProperties() {
    if (!propertiesLoaded) {
        HashMap var0 = allFonts;
        synchronized(allFonts) {
            if (!propertiesLoaded) {
                try {
                    loadRegistry();//看这里
                    Iterator i$ = ((Set)registryNames.get("fonts")).iterator();

                    while(i$.hasNext()) {
                        String font = (String)i$.next();
                        allFonts.put(font, readFontProperties(font));
                    }
                } catch (Exception var4) {
                    ;
                }

                propertiesLoaded = true;
            }
        }
    }
}

private static void loadRegistry() throws IOException {
    //加载cjk_registry.properties文件
    InputStream is = StreamUtil.getResourceStream("com/itextpdf/text/pdf/fonts/cmaps/cjk_registry.properties");
    Properties p = new Properties();
    p.load(is);
    is.close();
    Iterator i$ = p.keySet().iterator();

    while(i$.hasNext()) {
        Object key = i$.next();
        String value = p.getProperty((String)key);
        String[] sp = value.split(" ");
        Set<String> hs = new HashSet();
        String[] arr$ = sp;
        int len$ = sp.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            String s = arr$[i$];
            if (s.length() > 0) {
                hs.add(s);
            }
        }

        registryNames.put((String)key, hs);
    }

}

cjk_registry.properties如下

Adobe_Japan1=78-EUC-H 78-EUC-V 78-H 78-RKSJ-H 78-RKSJ-V 78-V 78ms-RKSJ-H 78ms-RKSJ-V 83pv-RKSJ-H 90ms-RKSJ-H 90ms-RKSJ-V 90msp-RKSJ-H 90msp-RKSJ-V 90pv-RKSJ-H 90pv-RKSJ-V Add-H Add-RKSJ-H Add-RKSJ-V Add-V Adobe-Japan1-0 Adobe-Japan1-1 Adobe-Japan1-2 Adobe-Japan1-3 Adobe-Japan1-4 Adobe-Japan1-5 Adobe-Japan1-6 EUC-H EUC-V Ext-H Ext-RKSJ-H Ext-RKSJ-V Ext-V H Hankaku Hiragana Katakana NWP-H NWP-V RKSJ-H RKSJ-V Roman UniJIS-UCS2-H UniJIS-UCS2-HW-H UniJIS-UCS2-HW-V UniJIS-UCS2-V UniJIS-UTF16-H UniJIS-UTF16-V UniJIS-UTF32-H UniJIS-UTF32-V UniJIS-UTF8-H UniJIS-UTF8-V UniJIS2004-UTF16-H UniJIS2004-UTF16-V UniJIS2004-UTF32-H UniJIS2004-UTF32-V UniJIS2004-UTF8-H UniJIS2004-UTF8-V UniJISPro-UCS2-HW-V UniJISPro-UCS2-V UniJISPro-UTF8-V UniJISX0213-UTF32-H UniJISX0213-UTF32-V UniJISX02132004-UTF32-H UniJISX02132004-UTF32-V V WP-Symbol
Adobe_Korea1=Adobe-Korea1-0 Adobe-Korea1-1 Adobe-Korea1-2 KSC-EUC-H KSC-EUC-V KSC-H KSC-Johab-H KSC-Johab-V KSC-V KSCms-UHC-H KSCms-UHC-HW-H KSCms-UHC-HW-V KSCms-UHC-V KSCpc-EUC-H KSCpc-EUC-V UniKS-UCS2-H UniKS-UCS2-V UniKS-UTF16-H UniKS-UTF16-V UniKS-UTF32-H UniKS-UTF32-V UniKS-UTF8-H UniKS-UTF8-V
Adobe_GB1=Adobe-GB1-0 Adobe-GB1-1 Adobe-GB1-2 Adobe-GB1-3 Adobe-GB1-4 Adobe-GB1-5 GB-EUC-H GB-EUC-V GB-H GB-V GBK-EUC-H GBK-EUC-V GBK2K-H GBK2K-V GBKp-EUC-H GBKp-EUC-V GBpc-EUC-H GBpc-EUC-V GBT-EUC-H GBT-EUC-V GBT-H GBT-V GBTpc-EUC-H GBTpc-EUC-V UniGB-UCS2-H UniGB-UCS2-V UniGB-UTF16-H UniGB-UTF16-V UniGB-UTF32-H UniGB-UTF32-V UniGB-UTF8-H UniGB-UTF8-V
Adobe_CNS1=Adobe-CNS1-0 Adobe-CNS1-1 Adobe-CNS1-2 Adobe-CNS1-3 Adobe-CNS1-4 Adobe-CNS1-5 Adobe-CNS1-6 B5-H B5-V B5pc-H B5pc-V CNS-EUC-H CNS-EUC-V CNS1-H CNS1-V CNS2-H CNS2-V ETen-B5-H ETen-B5-V ETenms-B5-H ETenms-B5-V ETHK-B5-H ETHK-B5-V HKdla-B5-H HKdla-B5-V HKdlb-B5-H HKdlb-B5-V HKgccs-B5-H HKgccs-B5-V HKm314-B5-H HKm314-B5-V HKm471-B5-H HKm471-B5-V HKscs-B5-H HKscs-B5-V UniCNS-UCS2-H UniCNS-UCS2-V UniCNS-UTF16-H UniCNS-UTF16-V UniCNS-UTF32-H UniCNS-UTF32-V UniCNS-UTF8-H UniCNS-UTF8-V
Adobe_Japan1_Uni=UniJIS-UTF16-H UniJIS-UTF16-V
Adobe_Korea1_Uni=UniKS-UTF16-H UniKS-UTF16-V
Adobe_GB1_Uni=UniGB-UTF16-H UniGB-UTF16-V
Adobe_CNS1_Uni=UniCNS-UTF16-H UniCNS-UTF16-V
fonts=HeiseiMin-W3 HeiseiKakuGo-W5 KozMinPro-Regular STSong-Light STSongStd-Light MHei-Medium MSung-Light MSungStd-Light HYGoThic-Medium HYSMyeongJo-Medium HYSMyeongJoStd-Medium

OK,到此为止,通过查看源码可知com.lowagie.text.pdf.BaseFont去加载不存在的文件而获取不到"STSong-Light"和"UniGB-UCS2-H"配置值而抛出异常;
而com.itextpdf.text.pdf.BaseFont返回的结果为
(BaseFont)new CJKFont(name, encoding, embedded);
时间紧迫,文笔不好。-_-

<think>我们计划使用Spring Boot来生成PDF申请表。根据引用[4],我们可以使用Spring MVC的视图技术来实现PDF导出。常见的库有iText和Apache PDFBox。这里我们选择iText,因为它功能强大且广泛使用。 步骤: 1. 添加依赖:在pom.xml中添加iText相关依赖。 2. 创建PDF视图:创建一个视图类,继承AbstractPdfView(需要spring-support库,它包含对iText的支持)。 3. 实现生成PDF的方法:在该视图类中重写buildPdfDocument方法,使用iText API生成PDF内容。 4. 创建控制器:编写一个控制器方法,返回该视图并传递所需数据。 但是,注意引用[4]提到AbstractXlsxStreamingView用于Excel导出,而PDF导出可以使用AbstractPdfView。另外,Spring Boot 2.x之后,官方不再直接提供对iText的集成,因此我们可能需要直接使用iText,并通过ResponseBody自行处理输出流,或者使用其他库如OpenPDF(基于iText的开源分支)或PDFBox。 这里我们使用OpenPDF,因为它是iText的一个分支,且许可证更友好。 具体步骤: 1. 添加依赖: ```xml <dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> <version>1.3.30</version> </dependency> ``` 2. 创建一个工具类或服务类来生成PDF。 3. 在控制器中,生成PDF并写入HttpServletResponse输出流。 示例代码: 首先,创建一个PDF生成服务: ```java import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Paragraph; import com.lowagie.text.pdf.PdfWriter; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; @Service public class PdfService { public byte[] generatePdf(String content) throws DocumentException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Document document = new Document(); PdfWriter.getInstance(document, outputStream); document.open(); document.add(new Paragraph(content)); // 可以添加更复杂的内容,如图片、表格等 document.close(); return outputStream.toByteArray(); } } ``` 然后,在控制器中: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController public class PdfController { @Autowired private PdfService pdfService; @GetMapping("/exportPdf") public ResponseEntity<byte[]> exportPdf() throws IOException { try { byte[] pdfBytes = pdfService.generatePdf("这是一个申请表示例"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData("attachment", "application_form.pdf"); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); return ResponseEntity.ok() .headers(headers) .body(pdfBytes); } catch (Exception e) { throw new IOException("生成PDF失败", e); } } } ``` 这样,当访问/exportPdf时,浏览器会下载名为application_form.pdf的文件。 注意:以上代码仅生成一个简单的段落。实际申请表中可能需要更复杂的格式,如表格、图片、样式等。我们可以使用iText/OpenPDF提供的更多元素来构建。 例如,添加表格: ```java import com.lowagie.text.Element; import com.lowagie.text.pdf.PdfPTable; // ... // 在document.open()之后 PdfPTable table = new PdfPTable(3); // 3列 table.addCell("姓名"); table.addCell("张三"); table.addCell("年龄"); table.addCell("25"); table.addCell("职业"); table.addCell("工程师"); table.setHorizontalAlignment(Element.ALIGN_LEFT); document.add(table); ``` 另外,为了支持中文,需要设置中文字体。我们可以将字体文件(如simsun.ttf)放在resources/fonts目录下,然后使用: ```java import com.lowagie.text.Font; import com.lowagie.text.FontFactory; import com.lowagie.text.pdf.BaseFont; // 在生成PDF的方法中,先注册中文字体 FontFactory.register("classpath:fonts/simsun.ttf", "simsun"); Font chineseFont = FontFactory.getFont("simsun", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, 12); // 然后在段落中使用 document.add(new Paragraph("中文内容", chineseFont)); ``` 注意:OpenPDF的字体注册方式可能有所不同,也可以使用: ```java BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); Font chineseFont = new Font(baseFont, 12); ``` 但是,STSong-Light是iText Asian.jar中的字体,OpenPDF可能没有内置。因此,建议使用字体文件。 另外,也可以使用itext-asian(注意许可证): ```xml <dependency> <groupId>com.lowagie</groupId> <artifactId>itext-asian</artifactId> <version>2.1.7</version> </dependency> ``` 然后使用: ```java BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); ``` 但是,由于OpenPDF是iText的一个分支,可能不完全兼容。因此,更推荐使用字体文件的方式。 总结:在Spring Boot中生成PDF申请表,我们可以通过上述步骤实现。首先添加OpenPDF依赖,然后创建服务类生成PDF字节数组,最后在控制器中输出到响应流。 注意事项: 1. 处理中文字体问题,确保PDF中能正确显示中文。 2. 根据申请表的具体格式,使用iText/OpenPDF的API构建复杂的PDF结构。 如果申请表非常复杂,也可以考虑使用模板引擎(如Thymeleaf)生成HTML,然后使用Flying Saucer(基于iText)将HTML转换为PDF。但这种方式需要额外的依赖,且对CSS支持有限。 这里我们仅展示了直接使用OpenPDF的方式,适用于对格式控制要求较高的场景。 §§相关问题§§ 1. 如何在Spring Boot中生成带有表格和图片的PDF? 2. 如何解决PDF生成中的中文字体显示问题? 3. 除了OpenPDF,还有哪些常用的Java生成PDF的库? 4. 如何通过HTML模板生成PDF?</think>在 Spring Boot 项目中生成和导出 PDF 申请表,可以通过以下步骤实现: ### 解决方案步骤 1. **添加依赖** 在 `pom.xml` 中添加 PDF 生成库依赖(推荐 iText 或 Apache PDFBox): ```xml <!-- iText 核心库 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.3</version> </dependency> <!-- HTML 转 PDF 支持(可选) --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.5</version> </dependency> ``` 2. **创建 PDF 生成服务** ```java import com.itextpdf.text.*; import com.itextpdf.text.pdf.PdfWriter; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; @Service public class PdfGenerationService { public byte[] generateApplicationForm(String applicantName) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Document document = new Document(); PdfWriter.getInstance(document, outputStream); document.open(); // 添加标题 Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18); Paragraph title = new Paragraph("申请表", titleFont); title.setAlignment(Element.ALIGN_CENTER); document.add(title); // 添加申请人信息 document.add(new Paragraph("\n")); // 空行 Font contentFont = FontFactory.getFont(FontFactory.HELVETICA, 12); document.add(new Paragraph("申请人: " + applicantName, contentFont)); document.add(new Paragraph("申请日期: " + new java.util.Date(), contentFont)); // 添加表格(示例) com.itextpdf.text.pdf.PdfPTable table = new com.itextpdf.text.pdf.PdfPTable(2); table.addCell("项目"); table.addCell("内容"); table.addCell("联系方式"); table.addCell("138****1234"); document.add(table); document.close(); return outputStream.toByteArray(); } } ``` 3. **创建控制器处理导出请求** ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class PdfExportController { @Autowired private PdfGenerationService pdfService; @GetMapping("/export/application-form") public ResponseEntity<byte[]> exportPdf( @RequestParam String applicantName) throws Exception { byte[] pdfBytes = pdfService.generateApplicationForm(applicantName); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData("attachment", "application_form.pdf"); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); return ResponseEntity.ok() .headers(headers) .body(pdfBytes); } } ``` 4. **前端调用示例** (React) ```jsx function downloadPdf() { const applicantName = "张三"; // 实际从表单获取 fetch(`/export/application-form?applicantName=${encodeURIComponent(applicantName)}`) .then(response => response.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'application_form.pdf'; document.body.appendChild(a); a.click(); a.remove(); }); } ``` ### 关键实现说明 1. **动态内容生成**: - 通过 `PdfGenerationService` 动态填充申请人信息、日期等 - 支持表格、段落、图片等复杂布局(使用 iText API) 2. **导出优化**: - 使用 `ByteArrayOutputStream` 避免文件系统操作 - 设置正确的 HTTP 响应头实现浏览器直接下载 - 中文支持需添加中文字体: ```java BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); Font chineseFont = new Font(baseFont, 12); document.add(new Paragraph("中文内容", chineseFont)); ``` 3. **替代方案**: - 使用 **Thymeleaf + Flying Saucer** 通过 HTML 模板生成 PDF - 使用 **JasperReports** 生成复杂报表式 PDF ### 性能优化建议 1. 对于大数据量导出,使用 `SXSSFWorkbook` 模式(类似 Excel 导出的优化方案)[^4] 2. 添加缓存机制避免重复生成 3. 使用异步处理导出任务 > 提示:实际项目中建议将 PDF 模板设计为可配置化,通过数据库或配置文件管理模板结构[^2]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值