Java實(shí)現(xiàn)Markdown轉(zhuǎn)為PDF的兩種方式
推薦兩種方式:
方式一:使用spire.doc框架(非常簡(jiǎn)單,但需要商業(yè)授權(quán))
- (需要商業(yè)授權(quán),沒(méi)有授權(quán)不能商用,且生成開(kāi)頭有標(biāo)記)
- 也有免費(fèi)版本spire.doc.free(但缺少很多功能)
方式二:markdown -》 html -》pdf(開(kāi)源,但要自己實(shí)現(xiàn)轉(zhuǎn)換)
- 可以將markdown先轉(zhuǎn)為html,在將html轉(zhuǎn)為pdf的方式,實(shí)現(xiàn)兩者的轉(zhuǎn)換
方式一:spire框架實(shí)現(xiàn)
引入jar包
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc</artifactId>
<version>13.8.7</version>
</dependency>需要定義一下倉(cāng)庫(kù)地址
<repositories>
<repository>
<id>com.e-iceblue</id>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>定義轉(zhuǎn)換代碼
public static void main(String[] args) {
Document document = new Document();
document.loadFromFile("C:\\Users\\Administrator\\Desktop\\test111\\123.md", FileFormat.Markdown);
document.saveToFile("C:\\Users\\Administrator\\Desktop\\test111\\123.docx", FileFormat.PDF);
}成功,非常簡(jiǎn)單,且可以指定為FileFormat.Docx轉(zhuǎn)為word,但需要商業(yè)授權(quán)
方式二:markdown -》 html -》pdf
這種方式有很多框架可以實(shí)現(xiàn),可以自由搭配,主要能實(shí)現(xiàn)markdown轉(zhuǎn)為html,html轉(zhuǎn)為pdf即可
搭配一:commonmark + openhtmltopdf
- commonmark:將markdown轉(zhuǎn)為html
- openhtmltopdf:將html轉(zhuǎn)為pdf
- jsoup:用來(lái)糾正html格式(因?yàn)閙arkdown填寫(xiě)的內(nèi)容并不是符合條件的,比如代碼塊外定義標(biāo)簽)
<!-- CommonMark -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.18.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.18.0</version>
</dependency>
<!-- OpenHTMLtoPDF (基于 iText 7) -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-core</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>實(shí)現(xiàn)
- 將 Markdown 轉(zhuǎn)換為 HTML 片段
- 將 HTML 中的本地圖片嵌入為 Base64 Data URI(這個(gè)如果不需要圖片的,可以不用加)
- 包裝為完整的 HTML 文檔(含 CSS 樣式,css樣式模板)
- 轉(zhuǎn)換為 XHTML 兼容格式(用來(lái)保證轉(zhuǎn)化的html格式正確)
- 將 XHTML 渲染為 PDF
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Entities;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
public class TestPDF {
public static void main(String[] args) throws Exception {
String inputPath = "C:\\Users\\Administrator\\Desktop\\test111\\456.md";
String outputPath = "C:\\Users\\Administrator\\Desktop\\test111\\456.pdf";
// 1. 將 Markdown 轉(zhuǎn)換為 HTML 片段
String htmlFragment = convertMarkdownToHtml(inputPath);
// 2. 將 HTML 中的本地圖片嵌入為 Base64 Data URI
String embeddedHtml = embedLocalImagesAsDataUri(htmlFragment, inputPath);
// 3. 包裝為完整的 HTML 文檔(含 CSS 樣式)
String fullHtml = wrapWithCompleteHtml(embeddedHtml);
// 4. 轉(zhuǎn)換為 XHTML 兼容格式
String xhtmlContent = convertToXhtml(fullHtml);
// 5. 將 XHTML 渲染為 PDF
renderXhtmlToPdf(xhtmlContent, outputPath);
}
/**
* 將 XHTML 內(nèi)容渲染為 PDF 文件。
*/
private static void renderXhtmlToPdf(String xhtmlContent, String outputPath) throws IOException {
// Step 4: 使用 OpenHTMLtoPDF (PDFBox 后端) 轉(zhuǎn) PDF
try (OutputStream os = new FileOutputStream(outputPath)) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode(); // 可選:提升速度
builder.withHtmlContent(xhtmlContent, null); // 第二個(gè)參數(shù)是 baseUri,用于解析相對(duì)路徑資源(如圖片)
builder.toStream(os);
String lowerCase = System.getProperty("os.name").toLowerCase();
if (lowerCase.contains("win")) {
// Windows: 嘗試宋體、微軟雅黑
tryRegisterFont(builder, "C:/Windows/Fonts/simsun.ttc", "SimSun");
tryRegisterFont(builder, "C:/Windows/Fonts/msyh.ttc", "Microsoft YaHei");
} else if (lowerCase.contains("nix") || lowerCase.contains("nux") || lowerCase.contains("mac")) {
// Linux / macOS: 常見(jiàn)中文字體
// Noto Sans CJK (Google)
tryRegisterFont(builder, "/usr/share/fonts/noto/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC");
tryRegisterFont(builder, "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC");
// WenQuanYi Micro Hei (文泉驛微米黑)
tryRegisterFont(builder, "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "WenQuanYi Micro Hei");
tryRegisterFont(builder, "/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc", "WenQuanYi Micro Hei");
// macOS
if (lowerCase.contains("mac")) {
tryRegisterFont(builder, "/System/Library/Fonts/PingFang.ttc", "PingFang SC");
tryRegisterFont(builder, "/System/Library/Fonts/Helvetica.ttc", "Helvetica");
}
}
builder.run();
}
System.out.println("PDF 已生成: " + outputPath);
}
/**
* 將 HTML 字符串轉(zhuǎn)換為 XHTML 兼容格式。
*/
private static String convertToXhtml(String html) {
Document doc = Jsoup.parse(html);
doc.outputSettings()
.syntax(Document.OutputSettings.Syntax.xml) // 輸出 XML 語(yǔ)法
.escapeMode(Entities.EscapeMode.xhtml) // 實(shí)體轉(zhuǎn)義(如 & → &)
.prettyPrint(false); // 禁用美化,避免多余空白
return doc.html();
}
/**
* 讀取 Markdown 文件并將其轉(zhuǎn)換為 HTML 片段。
*/
private static String convertMarkdownToHtml(String markdownFilePath) throws IOException {
// Step 1: 讀取 Markdown 文件
byte[] bytes = Files.readAllBytes(Paths.get(markdownFilePath));
String markdown = new String(bytes, "UTF-8");
// Step 2: 使用 CommonMark 解析并轉(zhuǎn)為 HTML(支持表格)
Parser parser = Parser.builder()
.extensions(Arrays.asList(TablesExtension.create()))
.build();
HtmlRenderer renderer = HtmlRenderer.builder()
.extensions(Arrays.asList(TablesExtension.create()))
.build();
String html = renderer.render(parser.parse(markdown));
return html;
}
/**
* 將 HTML 片段包裝為完整的 HTML 文檔,包含內(nèi)聯(lián) CSS 樣式。
*/
private static String wrapWithCompleteHtml(String htmlFragment) {
// Step 3: 構(gòu)建完整 HTML(含 CSS)
String css =
"body {\n" +
" font-family: 'Noto Sans SC', SimSun, Arial, sans-serif;\n" +
" margin: 40px;\n" +
" line-height: 1.6;\n" +
" color: #333;\n" +
" font-size: 12pt;\n" +
"}\n" +
"h1, h2, h3, h4, h5, h6 {\n" +
" color: #2c3e50;\n" +
" margin-top: 1.2em;\n" +
" margin-bottom: 0.6em;\n" +
"}\n" +
"h1 { font-size: 24pt; }\n" +
"h2 { font-size: 20pt; }\n" +
"h3 { font-size: 16pt; }\n" +
"\n" +
"p, ul, ol, pre, table {\n" +
" margin: 0 0 16px 0;\n" +
"}\n" +
"\n" +
"a {\n" +
" color: #3498db;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
"strong { font-weight: bold; }\n" +
"em { font-style: italic; }\n" +
"del { text-decoration: line-through; }\n" +
"\n" +
"/* 表格:自適應(yīng) + 邊框 */\n" +
"table {\n" +
" border-collapse: collapse;\n" +
" width: 100%;\n" +
" empty-cells: show;\n" +
"}\n" +
"th, td {\n" +
" border: 1px solid #999;\n" +
" padding: 8px 12px;\n" +
" text-align: left;\n" +
" vertical-align: top;\n" +
"}\n" +
"th {\n" +
" background-color: #f0f0f0;\n" +
" font-weight: bold;\n" +
"}\n" +
"\n" +
"/* 代碼塊 */\n" +
"code {\n" +
" font-family: Consolas, Monaco, monospace;\n" +
" background-color: #f8f8f8;\n" +
" padding: 2px 4px;\n" +
" border-radius: 3px;\n" +
" font-size: 11pt;\n" +
"}\n" +
"pre {\n" +
" background-color: #f8f8f8;\n" +
" padding: 12px;\n" +
" border-left: 4px solid #ccc;\n" +
" overflow: hidden;\n" +
" white-space: pre-wrap;\n" +
" font-family: Consolas, Monaco, monospace;\n" +
" font-size: 10pt;\n" +
"}\n" +
"\n" +
"/* 列表 */\n" +
"ul, ol {\n" +
" padding-left: 24px;\n" +
"}\n" +
"li {\n" +
" margin-bottom: 6px;\n" +
"}\n" +
"\n" +
"/* 圖片(可選) */\n" +
"img {\n" +
" max-width: 100%;\n" +
" height: auto;\n" +
"}\n";
// 可選:包裝成完整 HTML(帶 CSS)
String fullHtml =
"<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\" />\n" +
" <title>Markdown to PDF</title>\n" +
" <style type=\"text/css\">\n" +
css + // ← 直接插入 css 變量(不通過(guò) %s)
" </style>\n" +
"</head>\n" +
"<body>\n" +
" " + htmlFragment + "\n" + // ← 直接插入 html 變量
"</body>\n" +
"</html>";
return fullHtml;
}
/**
* 嘗試注冊(cè)指定路徑的字體到 PDF 渲染器。
*/
private static void tryRegisterFont(PdfRendererBuilder builder, String fontPath, String fontFamilyName) {
File fontFile = new File(fontPath);
if (fontFile.exists()) {
System.out.println("? 注冊(cè)字體: " + fontFamilyName + " (" + fontPath + ")");
builder.useFont(fontFile, fontFamilyName);
} else {
System.out.println("?? 字體未找到: " + fontPath);
}
}
/**
* 將 HTML 中引用的本地圖片轉(zhuǎn)換為 Base64 Data URI 并內(nèi)聯(lián)到 HTML 中。
*/
private static String embedLocalImagesAsDataUri(String html, String markdownFilePath) throws IOException {
Path markdownBasePath = Paths.get(markdownFilePath).getParent();
Document doc = Jsoup.parse(html);
for (Element img : doc.select("img")) {
String src = img.attr("src");
if (src == null || src.trim().isEmpty()) continue;
if (src.startsWith("data:")) continue; // 已內(nèi)聯(lián)
if (src.startsWith("http://") || src.startsWith("https://")) {
System.err.println("Warning: Remote image not embedded: " + src);
continue;
}
Path imgPath;
// >>>>>>>>>> 新增:支持絕對(duì) Windows 路徑(以盤(pán)符開(kāi)頭) <<<<<<<<<<
if (src.matches("^[A-Za-z]:\\\\.*")) { // 匹配 D:\xxx 形式
// 將反斜杠替換為正斜杠(兼容性更好)
src = src.replace("\\", "/");
imgPath = Paths.get(src);
} else {
// 相對(duì)路徑:相對(duì)于 Markdown 文件所在目錄
imgPath = markdownBasePath.resolve(src).normalize();
// 安全檢查:防止路徑穿越
if (!imgPath.startsWith(markdownBasePath)) {
throw new SecurityException("Invalid relative image path: " + src);
}
}
// >>>>>>>>>> END 新增 <<<<<<<<<<
if (!Files.exists(imgPath)) {
System.err.println("Image not found: " + imgPath);
continue;
}
byte[] imageBytes = Files.readAllBytes(imgPath);
String mimeType = Files.probeContentType(imgPath);
if (mimeType == null || !mimeType.startsWith("image/")) {
mimeType = "image/png";
}
String dataUri = "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
img.attr("src", dataUri);
}
return doc.body().html();
}
}搭配二:flexmark+flying-saucer-pdf
flexmark:將markdown轉(zhuǎn)為htmlflying-saucer-pdf:將html轉(zhuǎn)為pdf- jsoup:用來(lái)糾正html格式(因?yàn)閙arkdown填寫(xiě)的內(nèi)容并不是符合條件的,比如代碼塊外定義標(biāo)簽)
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.50.44</version>
</dependency>
<!-- Flying Saucer: HTML to PDF -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.22</version>
</dependency>實(shí)現(xiàn)
- 將 Markdown 轉(zhuǎn)換為 HTML 片段
- 將 HTML 中的本地圖片嵌入為 Base64 Data URI(這個(gè)如果不需要圖片的,可以不用加)
- 包裝為完整的 HTML 文檔(含 CSS 樣式,css樣式模板)
- 轉(zhuǎn)換為 XHTML 兼容格式(用來(lái)保證轉(zhuǎn)化的html格式正確)
- 將 XHTML 渲染為 PDF
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Entities;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
public class TestPDF {
public static void main(String[] args) throws Exception {
String inputPath = "C:\\Users\\Administrator\\Desktop\\test111\\456.md";
String outputPath = "C:\\Users\\Administrator\\Desktop\\test111\\456.pdf";
// 1. 將 Markdown 轉(zhuǎn)換為 HTML 片段
String htmlFragment = convertMarkdownToHtml(inputPath);
// 2. 將 HTML 中的本地圖片嵌入為 Base64 Data URI
String embeddedHtml = embedLocalImagesAsDataUri(htmlFragment, inputPath);
// 3. 包裝為完整的 HTML 文檔(含 CSS 樣式)
String fullHtml = wrapWithCompleteHtml(embeddedHtml);
// 4. 轉(zhuǎn)換為 XHTML 兼容格式
String xhtmlContent = convertToXhtml(fullHtml);
// 5. 將 XHTML 渲染為 PDF
renderXhtmlToPdf(xhtmlContent, outputPath);
}
/**
* 將 XHTML 內(nèi)容渲染為 PDF 文件。
*/
private static void renderXhtmlToPdf(String xhtmlContent, String outputPath) throws IOException {
// Step 4: 使用 OpenHTMLtoPDF (PDFBox 后端) 轉(zhuǎn) PDF
try (OutputStream out = new FileOutputStream(outputPath)) {
ITextRenderer pdfRenderer = new ITextRenderer();
// ?? 關(guān)鍵:注冊(cè)中文字體
ITextFontResolver fontResolver = pdfRenderer.getFontResolver();
// Windows 系統(tǒng)路徑(宋體)
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
// Windows: 宋體、微軟雅黑(.ttc 支持良好)
fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/msyh.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} else if (osName.contains("nix") || osName.contains("nux")) {
// Linux
fontResolver.addFont("/usr/share/fonts/noto/NotoSansCJK-Regular.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} else if (osName.contains("mac")) {
// macOS
fontResolver.addFont("/System/Library/Fonts/PingFang.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
}
pdfRenderer.setDocumentFromString(xhtmlContent);
pdfRenderer.layout();
pdfRenderer.createPDF(out);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
System.out.println("PDF 已生成: " + outputPath);
}
/**
* 將 HTML 字符串轉(zhuǎn)換為 XHTML 兼容格式。
*/
private static String convertToXhtml(String html) {
Document doc = Jsoup.parse(html);
doc.outputSettings()
.syntax(Document.OutputSettings.Syntax.xml) // 輸出 XML 語(yǔ)法
.escapeMode(Entities.EscapeMode.xhtml) // 實(shí)體轉(zhuǎn)義(如 & → &)
.prettyPrint(false); // 禁用美化,避免多余空白
return doc.html();
}
/**
* 讀取 Markdown 文件并將其轉(zhuǎn)換為 HTML 片段。
*/
private static String convertMarkdownToHtml(String markdownFilePath) throws IOException {
// Step 1: 讀取 Markdown 文件
byte[] bytes = Files.readAllBytes(Paths.get(markdownFilePath));
String markdown = new String(bytes, "utf-8");
// Step 1: Markdown -> HTML
MutableDataSet options = new MutableDataSet();
options.set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create())); // 注冊(cè) TablesExtension
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
String html = renderer.render(parser.parse(markdown));
return html;
}
/**
* 將 HTML 片段包裝為完整的 HTML 文檔,包含內(nèi)聯(lián) CSS 樣式。
*/
private static String wrapWithCompleteHtml(String htmlFragment) {
// Step 3: 構(gòu)建完整 HTML(含 CSS)
String css =
"body {\n" +
" font-family: 'Noto Sans SC', SimSun, Arial, sans-serif;\n" +
" margin: 40px;\n" +
" line-height: 1.6;\n" +
" color: #333;\n" +
" font-size: 12pt;\n" +
"}\n" +
"h1, h2, h3, h4, h5, h6 {\n" +
" color: #2c3e50;\n" +
" margin-top: 1.2em;\n" +
" margin-bottom: 0.6em;\n" +
"}\n" +
"h1 { font-size: 24pt; }\n" +
"h2 { font-size: 20pt; }\n" +
"h3 { font-size: 16pt; }\n" +
"\n" +
"p, ul, ol, pre, table {\n" +
" margin: 0 0 16px 0;\n" +
"}\n" +
"\n" +
"a {\n" +
" color: #3498db;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
"strong { font-weight: bold; }\n" +
"em { font-style: italic; }\n" +
"del { text-decoration: line-through; }\n" +
"\n" +
"/* 表格:自適應(yīng) + 邊框 */\n" +
"table {\n" +
" border-collapse: collapse;\n" +
" width: 100%;\n" +
" empty-cells: show;\n" +
"}\n" +
"th, td {\n" +
" border: 1px solid #999;\n" +
" padding: 8px 12px;\n" +
" text-align: left;\n" +
" vertical-align: top;\n" +
"}\n" +
"th {\n" +
" background-color: #f0f0f0;\n" +
" font-weight: bold;\n" +
"}\n" +
"\n" +
"/* 代碼塊 */\n" +
"code {\n" +
" font-family: 'Noto Sans SC', SimSun, Consolas, Monaco, monospace !important;\n" +
" background-color: #f8f8f8;\n" +
" padding: 2px 4px;\n" +
" border-radius: 3px;\n" +
" font-size: 11pt;\n" +
"}\n" +
"pre {\n" +
" font-family: 'Noto Sans SC', SimSun, Consolas, Monaco, monospace !important;\n" +
" background-color: #f8f8f8;\n" +
" padding: 12px;\n" +
" border-left: 4px solid #ccc;\n" +
" overflow: hidden;\n" +
" white-space: pre-wrap;\n" +
" font-size: 10pt;\n" +
"}\n" +
"\n" +
"/* 列表 */\n" +
"ul, ol {\n" +
" padding-left: 24px;\n" +
"}\n" +
"li {\n" +
" margin-bottom: 6px;\n" +
"}\n" +
"\n" +
"/* 圖片(可選) */\n" +
"img {\n" +
" max-width: 100%;\n" +
" height: auto;\n" +
"}";
// 可選:包裝成完整 HTML(帶 CSS)
String fullHtml =
"<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\" />\n" +
" <title>Markdown to PDF</title>\n" +
" <style type=\"text/css\">\n" +
css + // ← 直接插入 css 變量(不通過(guò) %s)
" </style>\n" +
"</head>\n" +
"<body>\n" +
" " + htmlFragment + "\n" + // ← 直接插入 html 變量
"</body>\n" +
"</html>";
return fullHtml;
}
/**
* 將 HTML 中引用的本地圖片轉(zhuǎn)換為 Base64 Data URI 并內(nèi)聯(lián)到 HTML 中。
*/
private static String embedLocalImagesAsDataUri(String html, String markdownFilePath) throws IOException {
Path markdownBasePath = Paths.get(markdownFilePath).getParent();
Document doc = Jsoup.parse(html);
for (Element img : doc.select("img")) {
String src = img.attr("src");
if (src == null || src.trim().isEmpty()) continue;
if (src.startsWith("data:")) continue; // 已內(nèi)聯(lián)
if (src.startsWith("http://") || src.startsWith("https://")) {
System.err.println("Warning: Remote image not embedded: " + src);
continue;
}
Path imgPath;
// >>>>>>>>>> 新增:支持絕對(duì) Windows 路徑(以盤(pán)符開(kāi)頭) <<<<<<<<<<
if (src.matches("^[A-Za-z]:\\\\.*")) { // 匹配 D:\xxx 形式
// 將反斜杠替換為正斜杠(兼容性更好)
src = src.replace("\\", "/");
imgPath = Paths.get(src);
} else {
// 相對(duì)路徑:相對(duì)于 Markdown 文件所在目錄
imgPath = markdownBasePath.resolve(src).normalize();
// 安全檢查:防止路徑穿越
if (!imgPath.startsWith(markdownBasePath)) {
throw new SecurityException("Invalid relative image path: " + src);
}
}
// >>>>>>>>>> END 新增 <<<<<<<<<<
if (!Files.exists(imgPath)) {
System.err.println("Image not found: " + imgPath);
continue;
}
byte[] imageBytes = Files.readAllBytes(imgPath);
String mimeType = Files.probeContentType(imgPath);
if (mimeType == null || !mimeType.startsWith("image/")) {
mimeType = "image/png";
}
String dataUri = "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
img.attr("src", dataUri);
}
return doc.body().html();
}
}以上就是Java實(shí)現(xiàn)Markdown轉(zhuǎn)為PDF的兩種方式的詳細(xì)內(nèi)容,更多關(guān)于Java Markdown轉(zhuǎn)為PDF的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MyBatis typeAliases元素標(biāo)簽(含注解方式)及其屬性、設(shè)置方式
這篇文章主要介紹了MyBatis typeAliases元素標(biāo)簽(含注解方式)及其屬性、設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java使用Swagger文檔報(bào)錯(cuò)“java.lang.NullPointerException:null”
文章描述了一個(gè)Java項(xiàng)目中使用Swagger文檔時(shí)遇到的問(wèn)題,每次啟動(dòng)都報(bào)錯(cuò),但不影響項(xiàng)目運(yùn)行,問(wèn)題原因可能是實(shí)體類(lèi)中@ApiModelProperty有相同的name值或修飾符誤寫(xiě),最終通過(guò)重寫(xiě)源碼代碼進(jìn)行判空處理解決了問(wèn)題,此經(jīng)驗(yàn)僅供參考2026-05-05
Java使用Apache?POI在Excel中創(chuàng)建下拉列表
在業(yè)務(wù)系統(tǒng)中,我們經(jīng)常需要導(dǎo)出包含下拉選擇框的Excel模板,用于規(guī)范數(shù)據(jù)錄入,Apache?POI作為Java操作Office文檔的主流工具,提供了完善的數(shù)據(jù)驗(yàn)證功能,本文將詳細(xì)介紹如何使用POI在Excel中創(chuàng)建下拉列表,感興趣的可以了解下2026-03-03
關(guān)于java.util.Random的實(shí)現(xiàn)原理詳解
Java實(shí)用工具類(lèi)庫(kù)中的類(lèi)java.util.Random提供了產(chǎn)生各種類(lèi)型隨機(jī)數(shù)的方法,下面這篇文章主要給大家介紹了關(guān)于java.util.Random實(shí)現(xiàn)原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2017-08-08
Mybatisplus多表關(guān)聯(lián)分頁(yè)查詢多種實(shí)現(xiàn)方式
本文主要介紹了Mybatisplus多表關(guān)聯(lián)分頁(yè)查詢多種實(shí)現(xiàn)方式,包括使用XML自定義SQL、Wrapper搭配自定義SQL、使用DTO與自定義SQL及結(jié)合PageHelper實(shí)現(xiàn)分頁(yè)查詢,感興趣的可以了解一下2025-03-03
springboot 實(shí)現(xiàn)Http接口加簽、驗(yàn)簽操作方法

