pdfboss_markdown/lib.rs
1//! Markdown to PDF composition for pdfboss, taking CommonMark+GFM source
2//! and a CSS theme as input.
3//! It produces a [`pdfboss_write::Pdf`] alongside a replace-and-report
4//! [`Report`] of anything sanitized along the way.
5//! Given the same markdown and options, [`to_pdf`] always emits the same
6//! bytes: no clock, no randomness, no environment dependence.
7
8pub mod block;
9mod emit;
10mod layout;
11pub mod report;
12mod table;
13mod wrap;
14
15use std::path::PathBuf;
16
17pub use block::{Block, CellAlign, ListItem, Run};
18pub use pdfboss_style::{StyleError, Theme};
19pub use pdfboss_write::{PageSize, Pdf};
20pub use report::Report;
21
22/// Errors raised while laying out or writing a Markdown-composed document.
23#[derive(Debug, thiserror::Error)]
24pub enum Error {
25 /// An image referenced by the document could not be loaded or decoded.
26 #[error("{path}: {message}")]
27 Image {
28 /// The image path or URI as it appeared in the document.
29 path: String,
30 /// A description of why the image could not be used.
31 message: String,
32 },
33 /// A lower-level PDF-writing failure.
34 #[error(transparent)]
35 Write(#[from] pdfboss_write::Error),
36}
37
38/// Composition options for [`to_pdf`].
39pub struct Options {
40 /// The CSS theme cascading over every element.
41 pub theme: Theme,
42 /// The page size every page is laid out and emitted at.
43 pub page_size: PageSize,
44 /// The directory local image paths resolve against.
45 pub base_dir: PathBuf,
46}
47
48impl Default for Options {
49 /// The built-in default theme, A4 pages, and the current directory as
50 /// the image base.
51 fn default() -> Options {
52 Options {
53 theme: Theme::default_theme(),
54 page_size: PageSize::A4,
55 base_dir: PathBuf::from("."),
56 }
57 }
58}
59
60/// Parses `markdown`, lays it out under `options`, and emits a
61/// [`pdfboss_write::Pdf`] ready to serialize, alongside a [`Report`] of
62/// unencodable characters replaced and raw HTML fragments skipped.
63pub fn to_pdf(markdown: &str, options: &Options) -> Result<(Pdf, Report), Error> {
64 let (blocks, skipped_html) = block::parse_blocks(markdown);
65 let mut report = Report {
66 skipped_html,
67 ..Report::default()
68 };
69 let laid = layout::layout(
70 &blocks,
71 &options.theme,
72 options.page_size,
73 &options.base_dir,
74 &mut report,
75 )?;
76 let pages = emit::emit(laid, options.page_size)?;
77 Ok((
78 Pdf {
79 pages,
80 ..Pdf::default()
81 },
82 report,
83 ))
84}