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.
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:
- GitHub-flavored Markdown
- The CommonMark Attributes extension
- YAML front matter as properties
- Fenced code blocks highlighted by tempest/highlight
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:
-
Typesetterio\Typesetter\Exceptions\InvalidConfigException: a problem with your config or content, like an unknown option, a missing theme or content folder, no content files, or two files with the same name. -
RuntimeException: a content file can't be read. -
Mpdf\MpdfImageException: an image is missing or broken.
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.