MarkdownToPdf is a Java library that converts Markdown to PDF.
Internally it uses commonmark-java to render Markdown to HTML, jsoup to produce well-formed XHTML, and OpenHTMLtoPDF to produce the PDF. SVG support is provided by Batik.
Requires JDK 21 or later.
import se.alipsa.md2pdf.Md2PdfEngine;
byte[] pdf = new Md2PdfEngine()
.markdown("# Hello\n\nWorld!")
.toPdf();
// Or write directly to a file:
new Md2PdfEngine()
.markdown(Path.of("report.md"))
.toPdf(Path.of("report.pdf"));<dependency>
<groupId>se.alipsa</groupId>
<artifactId>md2pdf</artifactId>
<version>0.1.0</version>
</dependency>The engine can be configured via a builder before use:
Md2PdfEngine engine = Md2PdfEngine.builder()
.tables(true) // enable GFM table support (default: true)
.softbreak("<br />\n") // how soft line breaks are rendered (default: <br />\n)
.build();Markdown can be provided as a String, File, Path, or InputStream:
// From a string
byte[] pdf = engine.markdown("# Report\n\nSome content").toPdf();
// From a file (relative image/CSS paths resolve from the file's directory)
byte[] pdf = engine.markdown(new File("reports/report.md")).toPdf();
// From an input stream
try (InputStream is = MyClass.class.getResourceAsStream("/report.md")) {
byte[] pdf = engine.markdown(is).toPdf();
}Terminal methods on the Renderer produce PDF or HTML:
Renderer job = engine.markdown("# Hello");
byte[] pdf = job.toPdf(); // byte array
job.toPdf(Path.of("out.pdf")); // file
job.toPdf(outputStream); // stream
String html = job.toHtml(); // string
job.toHtml(Path.of("out.html")); // fileUse css(...) to replace the default stylesheet entirely:
byte[] pdf = engine.markdown("# Report")
.css("body { font-family: serif; font-size: 12pt; }")
.toPdf();Use addCss(...) to extend the default stylesheet with overrides:
byte[] pdf = engine.markdown("# Report")
.addCss("h1 { color: #0057b8; }")
.toPdf();Both css(...) and addCss(...) also accept File, Path, URL, and InputStream:
byte[] pdf = engine.markdown("# Report")
.addCss(Path.of("style/overrides.css"))
.toPdf();When reading Markdown from a File or Path, relative image references are resolved
automatically from the Markdown file's directory:
// logo.png is read from reports/
byte[] pdf = engine.markdown(Path.of("reports/report.md")).toPdf();When using a Markdown string, set the base path explicitly:
byte[] pdf = engine.markdown("# Report\n\n")
.basePath(Path.of("reports"))
.toPdf();SVG is supported via Batik. You can embed SVG directly as a raw HTML block inside your Markdown file (Markdown passes through raw HTML unchanged):
## My chart
<div style="width:400px;height:300px">
<svg xmlns="http://www.w3.org/2000/svg">
<circle cx="150" cy="65" r="60" stroke="black" stroke-width="3" fill="red"/>
</svg>
</div>The SVG block must be associated with a block-level element (a <div>) with explicit
dimensions so that OpenHTMLtoPDF can allocate space for the Batik-rendered image.
A simple page header and footer can be added via the fluent API:
byte[] pdf = engine.markdown("# Alice's Adventures in Wonderland\n\nDown the Rabbit-Hole")
.pageHeader("Alice's Adventures in Wonderland")
.pageFooter("Page <span class=\"page-number\"></span> of <span class=\"total-pages\"></span>")
.pageMargins("0.75in")
.toPdf();When a header or footer is present and pageMargins(...) is omitted, md2pdf defaults
to 0.75in.
For more control, define running elements in your CSS and reference them from @page:
String css = """
div.header {
display: block;
position: running(header);
font-size: 9px;
text-align: right;
}
div.footer {
display: block;
position: running(footer);
font-size: 9px;
}
@page {
@top-center { content: element(header) }
@bottom-right { content: element(footer) }
}
#pagenumber:before { content: counter(page); }
#pagecount:before { content: counter(pages); }
""";Then include the header and footer divs as raw HTML in your Markdown:
<div class="header">Quarterly Report 2024</div>
<div class="footer">Page <span id="pagenumber"/> of <span id="pagecount"/></div>
# Chapter 1
Content here…byte[] pdf = engine.markdown("# Quarterly Report")
.title("Quarterly Report")
.author("Alipsa")
.subject("Sales")
.producer("Md2Pdf")
.toPdf();Register TTF font files and reference the family from CSS:
byte[] pdf = engine.markdown("# Font Test\n\nCustom font text")
.css("body { font-family: \"Jersey 25\"; }")
.font(new File("fonts/Jersey25-Regular.ttf"), "Jersey 25")
.toPdf();font(...) also accepts Path, URL, and InputStream.
Google Fonts typically distribute woff2 files, which OpenHTMLtoPDF does not support.
Use the TTF variant instead. You can find TTF URLs via the
Google Fonts TTF list.
byte[] pdf = engine.markdown("# Sofia font example\n\nHello world")
.addCss("""
@font-face {
font-family: "Sofia";
src: url(http://fonts.gstatic.com/s/sofia/v5/Imnvx0Ag9r6iDBFUY5_RaQ.ttf);
}
body { font-family: "Sofia"; }
""")
.toPdf();A desktop application for interactive Markdown editing and PDF generation is available in the gui module.
MIT — see LICENSE.
Note that this library depends on OpenHTMLtoPDF (LGPL v2.1+) and Batik (Apache 2.0). See the third-party section below for full details.
| Library | Purpose | License |
|---|---|---|
| commonmark-java | Markdown → HTML | BSD 2-Clause |
| OpenHTMLtoPDF | HTML/XHTML → PDF | LGPL 2.1+ |
| jsoup | HTML → well-formed XHTML | MIT |
| Batik | SVG rendering | Apache 2.0 |
| SLF4J | Logging facade | MIT |
| Library | Purpose | License |
|---|---|---|
| JUnit Jupiter | Test assertions | EPL 1.0 |