Skip to main content

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}