Markdown Presentation llms.txt llms-full.txt pages.json

The dialect and the library. The command line for both halves is mdoffice; this page is about what you get without it.

composer require markdown-office/markdown-presentation

PHP 8.2 or newer, with ext-dom, ext-gd and ext-zip. ext-imagick is optional and buys SVG.

One direction. Markdown to .pptx, and nothing back. There is no reader class, no reverse entry point, and PresentationRenderer has two methods: render() and overflows().


1. The Markdown dialect

Basic

Slidev-inspired. A deck is Markdown split on ---. The splitter is a port of Slidev's parseSync pinned to a commit, so a deck written for Slidev splits the same way here; the 21 places it deliberately does not are written down in Slidev, and where it differs.

PowerPointA deck with a title slide and a content slidebuilt by the library this page documents

This example did not build. The PowerPoint example "A deck with a title slide and a content slide" did not build: PHPPresentation is installed without the fixes this library needs. The library applies them as a patch when it is installed, which needs the plugin cweagans/composer-patches to be allowed in the project that installs this library.

The Markdown
# Markdown Office

Convert a Markdown document into a deck that opens in PowerPoint.

---

## Why a slide and not a page

- One `---`, one slide
- A theme decides the type, the colour and the grid
- `::click` builds the slide up on a click

Within a slide it is CommonMark plus tables, strikethrough and fenced code. --- separates slides and cannot be a thematic break — use ***.

---- is also a delimiter, so a YAML document as slide content needs a different fence.

Extended: frontmatter and themes

The deck's own configuration goes in the frontmatter of slide 1. Every key is in a closed set of seven, and anything outside it fails the build with a message naming what would have worked.

Key Type Default
title string null the slide's title
name string null a fallback for title
level integer ≥ 1 the heading's own depth the depth a title is drawn at
layout one of eight roles content which layout to use
theme string default slide 1 only
author string the theme's slide 1 only
description string null slide 1 only

The eight layout roles: title, content, two-column, section, blank, image-text, image-title, image-slide.

PowerPointEvery frontmatter key on a deckbuilt by the library this page documents

This example did not build. The PowerPoint example "Every frontmatter key on a deck" did not build: PHPPresentation is installed without the fixes this library needs. The library applies them as a patch when it is installed, which needs the plugin cweagans/composer-patches to be allowed in the project that installs this library.

The Markdown
---
theme: corporate-dark
title: The quarter in three slides
author: Markdown Office
description: What changed, what it cost, and what happens next.
---

# The quarter

## What changed

- Two products shipped
- Three customers renewed

The whole of theme.yml

Key Type Default
name string required, non-empty
description string null
author string null
dark boolean false — boolean only; dark: yes is refused
colors: mapping the twelve slots below
fonts: mapping Calibri / Calibri / Consolas
style: mapping the geometry below

The twelve colour slots — dk1 lt1 dk2 lt2 accent1…accent6 hlink folHlink — with four readable aliases accepted: dark1, light1, dark2, light2, hyperlink, followedHyperlink.

The whole of style:

Key Type Default Minimum
margin int, px 40 1
gutter int, px 20 1
block int, px 12 1
rule int, px 2 1
body int, pt 16 1
title int, pt 28 1
cover int, pt 40 1
section int, pt 44 1
headings list of int, pt [22, 20, 18, 16, 14] each ≥ 1
leading number 1.2 1.0
heading-weight bold or regular bold
heading-ink ink or accent ink

Four themes ship: default, corporate-dark, minimal, dense. minimal has more margin and less ink; dense has less margin and smaller type — those are the two axes a deck is usually fighting over, and they are separate keys so you can move one.

Resolution is a path, then <cwd>/themes/<name>, then the package's own. No silent fallback to default: a name that does not resolve raises ThemeNotFound listing the ones that do.

More, including layouts.yml and every built-in theme's numbers: Themes and layout.

Extended: directives

Four, and the set is closed — anything outside it is refused by name rather than drawn as a paragraph.

Written Does
::click advances the build by one step
::click=N advances to step N
:::click … ::: everything between the fences is at one step
::column moves the next blocks into the next column
::note … :: the slide's speaker note

All must be alone on their line. Names are case-insensitive.

PowerPointA slide that builds up, with a speaker notebuilt by the library this page documents

This example did not build. The PowerPoint example "A slide that builds up, with a speaker note" did not build: PHPPresentation is installed without the fixes this library needs. The library applies them as a patch when it is installed, which needs the plugin cweagans/composer-patches to be allowed in the project that installs this library.

The Markdown
## Three things

- One
- Two

::click

- Two is the interesting one
- Three

::click

- All three

::note
Say "two" slowly. That is the point of the slide.
::

::click=N must be greater than the current step and no more than 500. Step 1 is not a click — it is what is on the slide before anyone clicks — so a step nothing was written on costs no click.

::column takes no argument; the layout decides how many columns there are, and writing more markers than that is UnsupportedColumn.

The closer for :::click is exactly three colons. An unclosed region is refused rather than running to the end of the slide.

More, including the refusal table and the three things that look like they work and do not: Presentation directives.


2. Command line

There is none. The package declares no bin, and examples/build.php in the repository is the closest thing — it converts a directory of Markdown files and renders page images of each.

A shell loop is the whole substitute:

for f in decks/*.md; do
  php -r 'require "vendor/autoload.php";
    (new MarkdownPresentation\Render\PresentationRenderer())
      ->render((new MarkdownPresentation\Document\DeckParser())->parse(file_get_contents($argv[1])), $argv[2]);' \
    "$f" "${f%.md}.pptx"
done

The Word half has a real CLI — see Markdown Word → command line.


3. As a library

Two calls, and there is no single facade class.

use MarkdownPresentation\Document\DeckParser;
use MarkdownPresentation\Render\PresentationRenderer;

$deck = (new DeckParser())->parse($markdown);

(new PresentationRenderer())->render($deck, 'deck.pptx');

Reading the deck without writing it

Useful for a linter, an index, or a check that runs before anything is built:

$deck = (new DeckParser())->parse($markdown);

echo $deck->frontmatterValue('title'), "\n";
echo $deck->count(), " slides\n";

foreach ($deck->slides() as $slide) {
    echo $slide->index(), ' ', $slide->title(), "\n";
    echo $slide->note(), "\n";
}
0 One
1 Two

The rendering pipeline

render() checks every slide's frontmatter before anything is built, so a deck whose fourth slide names a directive nobody reads fails before it has written a byte. It then resolves the theme, resolves the layout roles, builds the masters, and writes one slide per source slide.

Five passes run over the written package afterwards — NotesColourMapPass, NotesChainPass, MeasureRoundingPass, PlaceholderPass and TimingPass — because PHPPresentation cannot write what PowerPoint needs. The rounding pass exists because a fractional a:spcPts value makes PowerPoint open the deck as [Repaired].

Ceilings

MAX_SLIDES 1000
MAX_STEP 500
MAX_PIXELS 16,777,216 — 4096 × 4096, enforced on the file header

The pixel ceiling is worth a moment. The obvious implementation of it is wrong: measuring a raster means decoding it, and decoding is what allocates the memory you were trying to bound. Both halves read the size from the header without drawing, so a 152-byte file declaring a 16000-square drawing is refused in 0.05 s with a 4 MB peak.

More, and the reason there is no second line of defence: Images.

Errors

MarkdownPresentation\Exception\Exception covers everything this half reports, so one catch does not also catch the failures of the libraries underneath. The failures a deck author can fix extend Exception\InvalidInput below it.

use MarkdownPresentation\Exception\Exception as DeckException;
use MarkdownPresentation\Exception\InvalidInput;

try {
    (new PresentationRenderer())->render($deck, 'out.pptx');
} catch (InvalidInput $error) {
    fwrite(STDERR, $error->getMessage());   // names what would have worked
}

One sits beside those two rather than under InvalidInput: Exception\UnreadablePackage. Nothing a deck names fixes a package that is not a package.

The whole catalogue is on Errors.


Next

Markdown Word · The directive syntax · Presentation directives · Themes and layout · Images · Errors

Every example on this page was built by the library it documents while this page was built. This page as Markdown.