mdbook-pdf: A mdBook backend for generating PDF files
Tags: rust, markdown, books
Published: 2022-01-30 07:09:48

## Introduction
[mdBook](https://github.com/rust-lang/mdBook) allows you to create book from markdown files. It's pretty much alike [Gitbook](https://www.gitbook.com/) but implemented in Rust. However, unlike Gitbook that supports using calibre for generating PDF, for a long time, mdBook [doesn't support generating PDF files natively](https://github.com/rust-lang/mdBook/issues/88), and supporting that is also not in their roadmap. Existing plugins (backends) such as [mdbook-latex](https://github.com/lbeckman314/mdbook-latex) that utilize [Tectonic](https://github.com/tectonic-typesetting/tectonic) as well as pandoc solutions will generate a PDF page that doesn't unify with the existing mdBook generated HTML version. Considering these facts, I created a mdBook backend named [mdbook-pdf](https://crates.io/crates/mdbook-pdf) for generating PDF based on [headless chrome](https://github.com/atroche/rust-headless-chrome) and [Chrome DevTools Protocol Page.printToPDF](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF).

mdbook-pdf depends on Google Chrome / Microsoft Edge / Chromium. The generated page are pretty much alike the one you manually print to PDF in your browser by opening `print.html` or executing `google-chrome-stable --headless --print-to-pdf=output.pdf file:///path/to/print.html`, but with customization of PDF paper orientation, scale of the webpage rendering, paper width and height, page margins, generated PDF page ranges, whether to display header and footer as well as customize their formats, and more, as well as automation. It supports all the platform where Google Chrome / Microsoft Edge / Chromium would work. You can check samples of the generated PDF files in the Artifacts [here](https://github.com/HollowMan6/mdbook-pdf/actions/runs/1766229211) (The Rust book collections generated in x86_64 Windows, macOS as well as Linux). 

## Installation & Usage
Since it's a plugin (backend) for [mdBook](https://github.com/rust-lang/mdBook), first of all you should ensure that `mdbook` is available.

If your machine's architecture is `x86_64`, or you are using Linux for `ARM64`, check the successful [build GitHub Actions workflows](https://github.com/HollowMan6/mdbook-pdf/actions/workflows/build.yml?query=is%3Asuccess), click into the latest one, and then you can get a binary from the Artifacts (including `Windows`, `Linux`, `macOS`).

Otherwise, make sure the [rust compiling environment](https://www.rust-lang.org/tools/install) is available, execute `cargo install mdbook-pdf` to compile and install.

If you want to compile the latest version, make sure the Rust build environment is available (`cargo build`).
Then run `git clone https://github.com/HollowMan6/mdbook-pdf.git`, in the cloned folder, run `cargo build --release` , get the executable in `target/release/`, and put it in PATH.

For running, have Google Chrome / Chromium / Microsoft Edge available (installed at the default location, in PATH or binary location configured) as currently, automatically downloading Chromium binary [isn't available](https://github.com/atroche/rust-headless-chrome/issues/286) (will update once upstream fixes such support).

- On Windows 10 and above, the program can generate PDF normally without installing any additional software, because Microsoft Edge is the browser provided with Windows system. Of course, considering the support for the older versions of Windows without Edge, you can install Google Chrome on your computer.
- In MacOS, you need to install [Google Chrome](https://www.google.com/chrome/) / [Microsoft Edge](https://www.microsoft.com/en-us/edge) or Chromium.
- In Linux, you can choose to install any of the Google Chrome / Chromium / Microsoft Edge browsers. It is recommended to install Chromium. The name of this software package in your Linux distribution is commonly `chromium` or `chromium-browser` (Note: for Ubuntu later than 18.04, you have to install `chromium-browser` through `snap`).

Make sure the following exists in your `book.toml`:

```toml
[output.html]

[output.pdf]
```

And also `[output.html.print]` is not disabled (it should be enabled by default, so don't worry if the following lines doesn't occur in you `book.toml`).

```toml
[output.html.print]
enable = true
```

A simplest `book.toml` is as follows:

```toml
[book]
title = "An Example"

[output.html]

[output.pdf]
```

Finally you can build your book and get the PDF file with `mdbook build` command, your PDF file will be available at `book/pdf/output.pdf`.

![An example for the output of the building progress](https://hollowmansblog.files.wordpress.com/2022/01/image.png)

## Configuration
Check [book.toml](https://github.com/HollowMan6/mdbook-pdf/blob/v0.1.0/test_doc/book.toml#L10-L33) and comments for details for the available configurations of `[output.pdf]`.

## Credits
This project relies on [headless_chrome](https://github.com/atroche/rust-headless-chrome). Because the new version has not been released, and the default timeout is not friendly to PDF generation, I use my [Fork version](https://github.com/HollowMan6/rust-headless-chrome) to publish [mdbook-pdf-headless_chrome](https://crates.io/crates/mdbook-pdf-headless_chrome) for expanding the relevant timeout to 300 seconds as a submodule of this project, thus enabling the project to be published on [Crates.io](https://crates.io/) as well.

## Some Notes and Thoughts
mdBook supports adding alternative backend. When the `mdbook build` command is invoked, if the `book.toml` in the book folder has an `[output.pdf]` item in addition to the default `[output.html]` for generating the html web page, `mdbook-pdf` will be called, and the relevant book information and parameter configuration in JSON are passed to the standard input of the program. Relevant mdBook documentation can be found [here](https://rust-lang.github.io/mdBook/for_developers/backends.html).

![Mechanism behind mdbook-pdf](https://hollowmansblog.files.wordpress.com/2022/01/1.drawio.png)

A [headless browser](https://en.wikipedia.org/wiki/Headless_browser) means that all the operations will be in the background without a graphical interface.

Before decided to use [Chrome DevTools Protocol Page.printToPDF](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF), I also tried [wkhtmltopdf](https://wkhtmltopdf.org/) which is based on QT4 Webkit. Hovewer, wkhtmltopdf doesn't seem to support [CSS Printing @media Rule](https://www.tutorialspoint.com/css/css_printing.htm), which will make some extra components visible and printed in the PDF.

I've also tried [directly making HTTP calls to the WebDriver in python](https://github.com/HollowMan6/mdbook-pdf/tree/webdriver-python) based on [W3C WebDriver Protocol Print Page](https://w3c.github.io/webdriver/#print-page), as well as using selenium for calling Chrome DevTools Protocol Page.printToPDF [in Python](https://github.com/HollowMan6/mdbook-pdf/tree/chrome-devtools-python). All those methods are not robust for large pages with the errors as follows for just the same reason for the original upstream [headless_chrome](https://github.com/atroche/rust-headless-chrome): the default timeout is 10 seconds, which is not friendly to large page PDF generation.

```bash
timeout: Timed out receiving message from renderer: 10.000
```

```bash
selenium.common.exceptions.WebDriverException: Message: unknown error: session deleted because of page crash
from unknown error: cannot determine loading status
from tab crashed
```

At present, I have tested that Firefox does not support using [headless_chrome](https://github.com/atroche/rust-headless-chrome) for PDF generation, and the Safari browser even does not support the [W3C WebDriver Protocol Print Page](https://w3c.github.io/webdriver/#print-page), let alone Chrome DevTools Protocol.

I've noticed that for some book, by using this backend, when click on some links that link inside the book, a html page that points to the original generated HTML storing path of the book will be opened, just as the [issue mentioned here](https://github.com/rust-lang/mdBook/issues/88#issuecomment-962613257). I guess for those "internal" links inside the book, work should be done in the mdbook side for `print.html` [referring here](https://blog.hubspot.com/marketing/jump-link-same-page) so that all the links linked "internally" would jump inside the generated `print.html`, as all the contents should already be on the `print.html`, there shouldn't be any hyperlinks that jump to other html files in the book. By resolving in this way, the generated PDF would also jump internally instead of opening a browser that won't connect to anything. I have already created [a PR for this](https://github.com/rust-lang/mdBook/pull/1738), hope it will get merged soon.

In addition, as I found that special characters such as `:` are very likely to appear in the title of the book, which will cause the generation of the related PDF file to fail, so the file name does not use the form of `<book name>.pdf` but `output.pdf`

Hope you enjoy it.