Typesetter.io
Github
Now on
GitHub!

Documentation and Guide

Convert Markdown to PDF using PHP. Use as a Composer library or a command line tool.

Quick Start Guide

Create ebooks, flyers, one-sheets and more from markdown! This PHP command converts your markdown into a PDF.

In this quick-start guide, we're going to look at creating an ebook from our markdown. The end result will be similar to our demo book.

Steps

First, initialize and version a folder using Git - you can git ignore the vendor folder if you wish (but you don't have to). Remember this is a book project, not necessarily a PHP package/project.

Then, install the CLI tool:

composer require typesetterio/typesetter-cli.

After it's installed, generate the default config file (you can customize this.) Run:

vendor/bin/typesetter init

Customize the config.php with the author and title and replace content/* with the contents for your book. The theme is my-theme/theme.html. If you want more customization, modify it with custom print media CSS. A cover.jpg or cover.html placed in my-theme/ becomes the cover.

Finally, generate the output by running vendor/bin/typesetter generate --output=generated/my-book-title.pdf and you can find your PDF in the generated folder.

Documentation Sections

Installation

This requires PHP 8.4 or above with the gd, mbstring, and dom extensions.

This tool is meant to be part of the repository that you have your written content. Because of that, you can install it into the local directory with the following command.

composer require typesetterio/typesetter-cli

If you want to just use the Typesetter library itself in your project without getting the command line tools, use the following:

composer require typesetterio/typesetter

This documentation focuses on the main functionality - which is using the command line option to generate ebooks. However, most of the configuration and methodology also applies to using the code as a library.

Docker

You can also run the command line tool from its Docker image. From your book directory, run:

docker run --rm -v "$PWD":/runtime ghcr.io/typesetterio/typesetter-cli generate

Your book directory is mounted at /runtime. The container runs as uid 1000. On Linux, if your user has a different uid, add --user "$(id -u):$(id -g)" so the written files are yours.

Images exist for linux/amd64 and linux/arm64, tagged <version>, <major>.<minor>, and latest.

✰

Basic Usage

Before we continue, note that there are two other great resources you may want to also look at.

First, you can look at the core library typesetterio/typesetter. This has the underlying service class and may give you more context. (You don't need to do that, though, if you just want to generate an ebook from your markdown on the command line.)

Second, check out the demo book in that repository to see one way of setting up a project.

Normally, this is meant to be used on the command line. The simplest way to use this would be like this:

vendor/bin/typesetter generate

But that's when everything is set up perfectly. We have some options to configure. Let's dig in.

Initialization

So, you installed it - now what? You can run the init command which will generate some content for you to review and modify.

vendor/bin/typesetter init

Then you'll find a local config.php that contains options for you to change. In addition, you'll receive a folder for your content. You can replace your content there.

Generation

After you've updated your configuration and put in your content, you can generate the book:

vendor/bin/typesetter generate

Boom! You'll find the output PDF directly in the same directory. Now you're done!

Ok now, where do you put your content? Or how do you make your own cover? That's covered next.

✰

Content

You probably want to make an ebook fo your own content and markdown, right? Let's dig in to this.

Cover

There are three options for a cover. The image or the html file should be in your theme folder (the theme config path), next to theme.html.

First, you can generate a cover image. The file should be named cover.jpg. The default configuration for MPDF is 96dpi. The default page is a A4. This means a standard resolution that fits that is 794 x 1123 (96dpi). You may experiment with different values for larger resolution displays. Remember, the larger in size your image, the larger the resulting PDF may be. The image is drawn at 210 x 297 mm (A4) whatever format you set in the mpdf option.

Second, you can generate a cover with HTML. The file should be named cover.html. For more details, reference the HTML processing of MPDF.

Finally, if you do not create either of these, Typesetter will automatically generate a cover page. It will use your title and author configuration options.

Markdown Content

The content folder holds the content that is coupled with your config to create the PDF.

Chapters should be generated in sequentially named files in your content folder. Refer to League Commonmark for more tips on writing and configuring markdown processors. Out of the box there is a pretty good configuration. This includes the following functionality:

A fence with no language or an unknown language renders as plain text. Indented code blocks are plain <pre><code>.

You can use sub-folders in your content folder. Chapters are sorted by file name only, not by folder, so every file name must be unique. Files from content-extra are sorted in with the rest, so name them to land where you want.

But that's not it. Coming up, check out command line options for generation, config customization to configure your book options, and advanced features.

✰

Command Line Options

By default, the Typesetter generate command will work without any options. However, there are some that you want to use.

Command Option Description
generate --config This is the path and filename relative to your current working directory of the config file. Default is config.php in the local directory.
generate --output This is the path and filename relative to your current working directory of the output file. Directories will be created. Default is the current directory with a slugified version of the title.

A missing config file or an invalid config prints the error message and exits with status 1.

Excellent! Let's move on to customizing the configuration of your project.

✰

Config Options

By default, the configuration used during generation is a config.php file in the local directory. You can customize this with command line options if you wish. If you're using the Typesetter library directly and without the CLI, these configuration options reflect the keys of the configuration array.

Config File Format

The configuration file should return an array from PHP. You may have other functionality in it as well (it is require'd during the processing of the command). It's recommended to use full namespaced class names when creating this file as the internal namespace may change as the project progresses.

Creating Config Directly for the Library

Unless you're using the Typesetter functionality as a library, you can skip ahead.

Create a config array and pass that to the config maker. Then create a new instance of the Typesetter class. Call the generate method with your config to get a PDF binary return from MPDF.

Here is an example:

$config = new \Typesetterio\Typesetter\Config([
    'title' => 'Benjamin Button',
    'author' => 'F. Scott Fitzgerald',
    'theme' => 'bb',
    'content' => 'content',
    'observers' => [
        new \Typesetterio\Typesetter\Observers\DefaultMarkdownConfiguration(),
        new \Typesetterio\Typesetter\Observers\FirstElementInChapterCssClass(),
        new \Typesetterio\Typesetter\Observers\BreakToPageBreak(),
        new \Typesetterio\Typesetter\Observers\Credits(),
    ],
]);

$service = new \Typesetterio\Typesetter\Typesetter();
$pdfContent = $service->generate($config);
file_put_contents('my-pdf.pdf', $pdfContent);

You can customize any of the functionality that you normally can in the config.php file directly in that array. Note that you must build the configuration option as detailed above in order to use the service.

Options and Descriptions

Whether you're using the command line or Typesetter as a library, you have the following options to customize:

Option Definition Default Example
title The title of your book. My Typeset Book Benjamin Button
author The author of your book. Joey Bubblegum F. Scott Fitzgerald
theme The path to the folder which contains your theme info. This should at minimum have a theme.html file in it. . bb
content The path to the folder with your markdown in it. . ./resources/content
content-filter A callable that is applied as a filter to each markdown file in content. It receives the SplFileInfo and the file name. Return true to keep the file, false to remove it. keeps every file fn() => true
content-extra A path that has extra content not associated directly with the main content. content-filter is not applied to it. none ./resources/content-extra
toc-enabled Should Table of Contents be generated after cover? true false
toc-links Should table of Contents link to the headers in your document? true false
toc-header The heading above the Table of Contents. It is an h1 with the id toc-header, and it also names the Table of Contents in the PDF outline. Table of Contents My ToC
toc-levels The heading tags (H1 to H6) to include in the Table of Contents and the PDF outline. ['H1', 'H2'] ['H1', 'H2', 'H3']
toc-attributes Extra attributes for MPDF's <tocpagebreak> tag. For example, ['resetpagenum' => 1] restarts page numbering after the Table of Contents. Your values win over the ones Typesetter sets from toc-header and toc-links. [] ['resetpagenum' => 1]
footer What text should be in the footer? You can use MPDF placeholders like {PAGENO} and {nbpg}, but not HTML. Footer is <footer class="footer"></footer> element. Leave empty to not generate a footer. The Table of Contents page never has one. {PAGENO} Page {PAGENO}
mpdf Options passed to MPDF, like format, orientation, tempDir, or the margin options. Typesetter starts with 27mm side margins and 14mm top and bottom. For fontDir and fontdata, see Fonts in the advanced section. An option MPDF does not know throws InvalidConfigException. [] ['format' => 'A5']
markdown-extensions An array of file extensions that indicate they should be rendered. Remember pages are generated in alphabetical or numerical order. ['md', 'markdown'] ['md']
observers An array of new instances of observers. If you set this, the default is replaced, so add DefaultMarkdownConfiguration yourself. You can read more about them in the advanced section. [new DefaultMarkdownConfiguration()]

Errors

Typesetter throws these exceptions:

You're all set. But, we're not done yet! Let's move on to the advanced section.

✰

Advanced

In this section, more advanced usage and configuration is covered.

Observers

Observers allow a decorator or visitor style of design pattern to interact with the process of generating this PDF. To make things as transparent as possible, some core items of the service are even configured in observers.

Observers must implement the Typesetterio\Typesetter\Contracts\Observer interface. You may extend the Typesetterio\Typesetter\Observers\Observer abstract class so you don't have to define every interface method. Then, you can override only the ones you want.

Observers are ran in the order they are registered.

Available observer methods available are the following:

Method Definition Parameters
markdownEnvironmentInitialized After the Commonmark Environment has been initialized, this will allow customization of it. League\CommonMark\Environment\Environment that you can add extensions or renderers.
pdfInitialized After the MPDF instance is created, you can customize it with this method. Mpdf\Mpdf
coverAdded After any cover has been added, this will allow customization of the PDF. For example, you may want to alter your cover image. Mpdf\Mpdf
chapterParsed After a chapter's markdown has been parsed into HTML and set into a Chapter. The chapter makes methods available to understand the context and modify the HTML. Note that the abstract observer class offers a getHtmlDocument() method that accepts a Chapter and returns a Dom\HTMLDocument, with your chapter's elements inside its body. When you're done, pass the Chapter and the document to setHtmlFromDocument() to save your changes. Typesetterio\Typesetter\Contracts\Chapter

The methods are called in the order listed above.

The following Observers are available for you. They should all be prefixed by Typesetterio\Typesetter\Observers:

Class Definition Parameters
BreakToPageBreak Puts a page break after the template tag {BREAK} The template to use - defaults to {BREAK}.
Credits This adds a "Created using Typesetter.io" box to the end of the last chapter with a class called credits-box. The theme from init centers it. You can remove this if you wish. The class to use on the box - default is credits-box.
DefaultMarkdownConfiguration This configures the markdown parser. You probably want to keep this one. Otherwise, if you'd like, you can generate a replacement to configure it on your own - which is only recommended for advanced users. It registers the Attributes and Front Matter extensions and the tempest/highlight fenced code renderer. None.
FirstElementInChapterCssClass Puts a css class of chapter-beginning on the first element of every chapter except the first. The class (defaults to chapter-beginning) and a boolean whether to skip the first chapter (default is true)

Chapter

The Typesetterio\Typesetter\Chapter class contains the content of your markdown for that specific chapter. Your observer receives it as the Typesetterio\Typesetter\Contracts\Chapter interface.

It contains a number of useful methods for you to interact with the content while it is being converted.

Method Definition
getHtml Get's the rendered HTML from markdown. Remember, this may have been modified by other observers first.
setHtml You must set the HTML after you've modified it. This will cast to string if it's not already.
getChapterNumber Returns an integer indicating which chapter number this is.
getTotalChapters Returns an integer indicating how many total chapters there is to be parsed.
isFirstChapter Returns a boolean indicating if this is the first chapter.
isLastChapter Returns a boolean indicating if you're dealing with the last chapter.
getMetaData If your content has YAML front matter, this will return an array of parsed properties from it. If not, an empty array. Front matter that is not a YAML mapping throws UnexpectedValueException.

If you'd like to just get some status updates or notifications and don't need to modify any content, check out Events.

Events

During the process of conversion, Typesetter issues some events that you may listen to. This can be useful for giving status updates or creating logs.

In order to register a listener, call the listen method with an event class and a callable. Your callable is passed the event. If the class is not an event, listen throws an InvalidListenerException.

Here's an example:

$service = new \Typesetterio\Typesetter\Typesetter();
$service->listen(\Typesetterio\Typesetter\Events\Finished::class, function () use ($myNotifier) {
    $myNotifier->alert('The process has finished');
});

At this time events do not contain any data. If you want to modify anything, please look at observers.

The following events are available (these are all going to be in the Typesetterio\Typesetter\Events namespace):

Event Definition
Starting When the generate() method is first called.
MarkdownInitialized After the markdown environment has been created and all observers have ran on it.
PdfInitialized After the MPDF instance has been created and lightly configured.
ThemeAdded When the theme has been found and applied to the PDF.
CoverImageAdded A cover image has been found in your theme and added.
CoverHtmlAdded A cover html file has been found in your theme and added.
CoverGenerated Typesetter was unable to find either cover option, so it generated one.
TocGenerated The Table of Contents has been generated and added.
FooterGenerated The footer content has been generated and added.
ContentGenerating The content has started rendering. This likely is the longest part of the process.
PdfRendering The PDF has started rendering from HTML to PDF binary. This may also take a long time.
Finished The whole process has finished and generate() is about to return the PDF.

You may listen to as many or as little events as you want. Remember, some of these are optional events based on your configuration.

Themes

Theming support is done with HTML and CSS. The theme folder should contain a theme.html which contains a <style> declaration as if it were in the head of an HTML file. This is where you can customize the theme of your PDF. Remember that you will be working with print styles in CSS so you can use things like page break.

You can also define named pages, like a landscape page, and put an element on one with the page property:

@page landscape {
    size: landscape;
    margin: 14mm 18mm 14mm 18mm;
}

.landscape-page {
    page: landscape;
}

In a @page rule you can set size, the margins, and a background.

You can style the Table of Contents too. The heading is h1#toc-header. Each entry is a div with the class mpdf_toc_level_0 for your first toc-levels tag, mpdf_toc_level_1 for the second, and so on. Inside it, the text is span.mpdf_toc_t_level_0 and the page number is span.mpdf_toc_p_level_0. Links are a.mpdf_toc_a. Use @page toc-page to change the margins of the Table of Contents page.

To start each chapter on a new page, use the FirstElementInChapterCssClass observer and add .chapter-beginning { page-break-before: always; } to your theme.

Style the footer with footer.footer. To remove the footer from one of your named pages, add this to its @page rule:

odd-footer-name: html_typesetter-no-footer;
even-footer-name: html_typesetter-no-footer;

Highlighted code is a <pre> tag with a data-lang attribute, like data-lang="php" (or txt when there is no language). The pieces of code are spans with hl- classes like hl-keyword, hl-comment, and hl-variable. The theme from the init command styles them, so start there.

The library's demo is a full working example: see demo/theme/theme.html and demo/run.php.

Fonts

MPDF's bundled font families work by name in your theme's CSS: dejavusans, dejavuserif, dejavusansmono, freesans, freeserif, freemono, and the rest of the MPDF Fonts Directory.

To add your own font, put the folder with its TTF files in fontDir and the file for each style in fontdata, both inside the mpdf option. The bundled fonts stay available. Then use the family name in theme.html.

'mpdf' => [
    'fontDir' => ['theme/fonts'],
    'fontdata' => [
        'ibmplexserif' => [
            'R' => 'IBMPlexSerif-Regular.ttf',
            'B' => 'IBMPlexSerif-Bold.ttf',
            'I' => 'IBMPlexSerif-Italic.ttf',
            'BI' => 'IBMPlexSerif-BoldItalic.ttf',
        ],
    ],
],

You're all set. Still need some help? You might try looking at the demo book in the library's repository to get some hints.

Still Have Questions?

Wire Aaron at once.