Skip to main content

bookbinder/
lib.rs

1//! Create pdf or epub books from markdown.
2//!
3//! # Basic Example
4//!
5//! ```
6//! use bookbinder::{BookSrc, BookSrcBuilder, create_epub, create_pdf, EpubOptions, LatexOptions};
7//!
8//! let src = BookSrcBuilder::new("A Book")
9//!     .author("A.N. Author")
10//!     .add_mainmatter("# Greetings\n\n Hello world...")
11//!     .process();
12//! let epub_options = EpubOptions::default();
13//! let epub = create_epub(src.clone(), epub_options)
14//!     .expect("Error producing epub");
15//!
16//! let pdf_options = LatexOptions::default();
17//! let pdf = create_pdf(src, pdf_options)
18//!     .expect("Error producing pdf");
19//!```
20//!
21//! # Why use this
22//!
23//! There are plenty of options for creating books from markdown;
24//! in the Rust world, there's [mdbook](https://docs.rs/mdbook/) and [crowbook](https://docs.rs/crowbook/); there's the amazing
25//! [Pandoc](https://pandoc.org) in Haskell.
26//!
27//! But these all tend to take Markdown constructs and turn them into books.
28//! This is great while it works, but books are complicated things. So, to take one simple example,
29//! a top-level markdown header (`# Header`) might represent a section, a chapter or a part
30//! depending on how a book is structured. Then it might need to be rendered with a label (`Chapter 1: Header`) or as a number alone (`I` or `One` or `1`),
31//! or as a title alone (`Header`)...
32//!
33//! If it looks more like `# Foreword` or `# Appendix`
34//! you've got another set of problems: in a proper book, these should display differently -- for example,
35//! page numbers in a foreword should probably be represented as roman numerals and any labels should refer
36//! to an appendix as `Appendix A`, not `Chapter 23`.
37//!
38//! Then you've got other difficulties -- most books will want a titlepage and a titlepage verso (copyright page),
39//! and they'll often feature things like epigraphs or a dedication. All of these need special formatting,
40//! but you can't really extend markdown indefinitely to include them all, or before you know it you've recreated TEI without the rigour.
41//!
42//! This crate relies on the insight that within a text divided into semantic roles, Markdown is an ideal solution -- you can say
43//! 'emphasise this text inside an epigraph' and all is well, but you can't say -- within Markdown itself --
44//! 'this text is an epigraph'. Since there aren't that an infinite number of possible parts to books, and things like [the epub structural semantics vocabulary](https://idpf.github.io/epub-vocabs/structure/) already have a range
45//! of defined possibilities, it's relatively easy to set up a container which renders Markdown within a
46//! more complex semantic system.
47//!
48//!
49//! And since some of these elements, like a titlepage or a copyright page, are pretty strictly the product
50//! of metadata, we further extend things so that they can be -- if you want -- generated automatically from the metadata
51//! which should already be included.
52//!
53//! In other words, you can do this:
54//!
55//! ```
56//! # use bookbinder::BookSrcBuilder;
57//! let introduction = "This is an introduction...";
58//! let dedication = "This is dedicated to someone";
59//! let foreword_with_custom_heading = "# A peculiar light\n\nForeword goes here...";
60//! let mainmatter = "# Early Life\n\n## I am born\n\nThe day of my birth was a dark cold day...";
61//!
62//! let src = BookSrcBuilder::new("A Book") // Start with a title
63//!     .author("A.N. Author")
64//!     .publisher("Publisher Name")
65//!     .add_introduction(introduction, None, "Introduction Author")
66//!     .set_dedication(dedication)
67//!     .add_foreword(foreword_with_custom_heading, None, vec!["First Foreword Author", "Second Foreword Author"])
68//!     .add_mainmatter(mainmatter);
69//! ```
70//!
71//! And get a book with a titlepage and copyright information, semantically aware presentation of details,
72//! and just really nice aesthetics.
73//!
74//! The example above would give you a halftitle,
75//! a titlepage, a copyright page explaining that this is copyright this year by A.N. Author and published by Publisher Name;
76//! then you'd get a sequence of frontmatter pages with niceties like roman page numbers,
77//! including an introduction with the header `Introduction` and a foreword with a nice label
78//! explaining it's a foreword but the header `A Peculiar Light`, as well as an unlabelled dedication which shows by its format
79//! (typically italicised text in a minipage, but you could specify otherwise) that it's a dedication.
80//! (Of course, if you wanted to avoid having things like a copyright page generated, or provide your own, that's easily done too!)
81//!
82//! The mainmatter following would treat `# Early Life` as a part header, and `# I am born` as a chapter header, but if they'd
83//! both been top-level headers they would have both been treated as chapters (or if one was a second-level header and the other a third and there were no top-level headers, they would have been treated as chapters and sections.)
84//!
85//! And so on. It's pretty cool.
86//!
87//! But even better is that it's easy to change how these basic semantic constructs are represented -- if you
88//! just want to ignore whatever chapters are titled and call them `One`, `Two` and `Three`, you can just set an
89//! option to do that! If you want your pdf book to be all in A4 paper and set in Comic Sans, but with headers in Papyrus -- many things
90//! are possible, but only some are well-advised.
91//!
92//! ```
93//! # use bookbinder_latex::PreambleOptions as LatexOptions;
94//! # use bookbinder_latex::PaperSize;
95//! let options = LatexOptions::default()
96//!     .use_words_for_chapter_labels() // this says 'Number chapters using words!'
97//!     .only_number_chapters() // And actually, while you're about it, don't do anything *but* number chapters
98//!     .set_serif_typeface("Comic Sans") // and set the main text in Comic Sans
99//!     .set_heading_typeface("Papyrus") // and headers in Papyrus
100//!     .set_papersize(PaperSize::A4Paper); // and an A4 book is a good size
101//! ```
102//!
103//! A book can be produced just by combining the options which can be applied to a particular output format with a source created through `BookSrcBuilder`.
104//!
105//! `BookSrcBuilder` lets you add a bewildering range of metadata (this was translated by a particular person, and someone else did the notes and gave an introduction, but the author was someone else again) and book elements (here's that introduction, here's the main text, here's a note by the translator).
106//!
107//! Meanwhile, there are a range of options for changing how this source is rendered. The options for different formats differ because what we can do with different formats also differs
108//! -- there's no point changing page numbers when epubs don't have page numbers, but there's not much sense
109//! in setting the cover image of a pdf file which doesn't have covers!
110//!
111//! # Goals
112//!
113//! This crate is meant to give an easy-to-use way to create nicely-formatted books
114//! which can handle quite granular complexity but shouldn't thrust that complexity
115//! on users who don't need it.
116//!
117//! It would be great to hear feedback on ways to make the user experience simpler, or things which are missing!
118//!
119//! # Our Markdown
120//!
121//! It's a slight exaggeration to say that we use simple Markdown -- actually, there are a couple of things books need
122//! which CommonMark doesn't have. So source strings or files are parsed using a markdown dialect, inspired by Pandoc, that
123//!
124//! - smartens straight quotes and replaces sequences of hyphens with appropriate dashes, and gives ellipses instead of `...`
125//! - includes footnotes
126//! - includes sub and superscript
127//!
128//! For full details, see [extended_pulldown](../extended_pulldown). Incidentally, an advantage of doing things this way is that it's very easy to
129//! build a pipeline `arbitrary input format -> pandoc -> markdown -> bookbinder`, so that books can be built from things like Word documents.
130//!
131//! # Technical details
132//!
133//! We use a custom solution for bundling epubs, but pdf files are produced by calling `XeLaTex` through
134//! `latexmk`. So you'll need LaTex installed to make pdfs! Also, if you want to include images in pdf format,
135//! you'll need to have `pdftocairo` installed.
136//!
137//! Architecturally, this crate is a very thin wrapper over:
138//!   1. `bookbinder_ast`, which sets out an abstract book source, and
139//!   2. `bookbinder_epub` and `bookbinder_latex`, which define how to render that source into a particular output format and the various options for such a rendering.
140//!
141//! So for full details of how something works, you'd best look to the specific crate!
142//! This seperated design is intended to allow different backends to be added in future --
143//! it'd be interesting to add a way to create an html book, and potentially
144//! an alternative way to produce pdfs could be valuable, since LaTeX is gorgeous but slow,
145//! and it's a big thing for people to install. The most likely candidates are an embedded version of
146//! either `neatroff` or `SILE`.
147//!
148//! # Deserialization
149//!
150//! This crate includes two functions `create_pdf_from_json` and `create_epub_from_json` which support
151//! deserializing and rendering from input json rather than controlling the process through manually building objects.
152//!
153//! The json used there represents a simplified format which offers a little less control over the process, primarily
154//! to make deserialization easier and more understandable.
155//!
156//! For full details, see the `deserialization` module.
157//!
158pub use bookbinder_ast::{BookSrc, BookSrcBuilder};
159use bookbinder_epub::EpubRenderer;
160pub use bookbinder_epub::Options as EpubOptions;
161pub use bookbinder_epub::RenderingError as EpubRenderingError;
162pub use bookbinder_latex::LatexSecNumDepth;
163use bookbinder_latex::PdfRenderer;
164pub use bookbinder_latex::PreambleOptions as LatexOptions;
165pub mod deserialization;
166
167/// Create an epub 3.2 from a `BookSrc` with the given options
168pub fn create_epub(src: BookSrc<'_>, options: EpubOptions) -> Result<Vec<u8>, EpubRenderingError> {
169    src.render_to_epub(options)
170}
171
172/// Create a pdf from a `BookSrc` with the given options
173pub fn create_pdf(src: BookSrc<'_>, options: LatexOptions) -> Result<Vec<u8>, std::io::Error> {
174    src.render_to_pdf_with_options(options)
175}
176
177/// Create an epub from a `BookSrc` with default options
178pub fn create_epub_default(src: BookSrc<'_>) -> Result<Vec<u8>, EpubRenderingError> {
179    src.render_to_epub_default()
180}
181
182/// Create a pdf from a `BookSrc` with default options
183pub fn create_pdf_default(src: BookSrc<'_>) -> Result<Vec<u8>, std::io::Error> {
184    src.render_to_pdf()
185}