slidown

package module
v0.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 25 Imported by: 0

README

slidown

slidown is a tool for creating PowerPoint (.pptx) presentations from Markdown.

It is a sibling project of k1LoW/deck, a Markdown → Google Slides tool by @k1LoW. slidown shares deck's philosophy of Markdown for content, slide tooling for design, and adopts the same Markdown format and element mapping. The difference is the output target: slidown writes standalone .pptx files via a pure-Go OOXML writer, with no third-party Office dependencies.

deck and slidown are designed to be used together or interchangeably from the same Markdown source, so you can pick the right delivery target — Google Slides or PowerPoint — without rewriting your slides.

Installation

Homebrew:

$ brew install Songmu/tap/slidown

go install:

$ go install github.com/Songmu/slidown/cmd/slidown@latest

Usage

Quick start

Write your slides in Markdown:

---
title: Talk about slidown
---

# First slide

## A subtitle

- a bullet point
- **bold** and *italic* and `code`
  - a nested point

---

# Second slide

A paragraph with a [link](https://example.com).

Then apply it to a .pptx:

$ slidown apply deck.md
Wrote deck.pptx (2 slide(s))

By default the output file is the input file name with a .pptx extension. Override it with --output/-o, or with the output frontmatter field:

$ slidown apply deck.md -o talk.pptx
Incremental updates

If the output .pptx already exists, apply updates it in place. Slides whose source content has not changed keep their existing slide parts verbatim, so manual edits made in PowerPoint to unchanged slides are preserved; only changed slides are regenerated:

$ slidown apply deck.md -o deck.pptx
Updated deck.pptx (2 slide(s))

Image-only differences (recompression, reordering or repositioning) are treated as unchanged, so adjusting images in PowerPoint does not trigger a regenerate.

To make reuse robust against page reordering, give a page a stable key — otherwise pages are matched by position:

# Overview

<!-- {"key": "overview"} -->

A page can also be frozen with the freeze page configuration. A frozen page keeps its existing slide as-is on rebuild even if its Markdown changed — useful for pinning a slide you have hand-tuned in PowerPoint:

# Hand-tuned slide

<!-- {"freeze": true} -->

To bring in a slide pasted from another presentation, declare a keyed, frozen placeholder page for it in the Markdown and paste the slide at that position in PowerPoint:

# Imported slide

<!-- {"key": "imported-architecture", "freeze": true} -->

On rebuild, apply pairs the placeholder with the pasted slide by position, keeps the pasted slide verbatim (freeze), and stamps the key onto it — so later rebuilds match it by key even after reordering. The deck source is authoritative for keys: a key renamed or removed in the Markdown is updated or cleared on the slide accordingly, and freeze: true need only stay in the Markdown.

Watch mode

Use --watch (or -w) to keep apply running and automatically re-apply when the deck markdown file changes:

$ slidown apply deck.md --watch
Wrote deck.pptx (2 slide(s))
Updated deck.pptx (2 slide(s))

slidown watches the deck file's directory so editor atomic-save patterns (rename/remove + create) are handled, filters events to the deck file, and debounces bursts so one save triggers one rebuild. If a rebuild fails, the error is printed and watch mode keeps running until you stop it with Ctrl-C.

--watch and --template are mutually exclusive. --template is only for initial generation; when you need watch mode with a template, set template in the config file instead.

MVP scope: watch mode currently tracks deck markdown file changes only.

Using a template

Supply a .pptx (or .potx) whose theme, slide masters and layouts should be used as the design when creating a new output file:

$ slidown apply deck.md --template theme.pptx

A template can only be supplied when the output file does not yet exist. It may also be set with the template config field. When the output already exists it is updated in place, reusing itself as the template, and passing --template is an error (choose a different --output, or remove the file first). See docs/templates.md for layout selection, inspecting available layouts with ls-layouts, and repurposing existing placeholders as subtitle targets. Templates can also include a style layout to customize inline syntax and table styling. Incremental updates reuse unchanged slides from the existing output file and still honor freeze: true.

Markdown file format

The Markdown used by slidown consists of an optional YAML frontmatter and a body. Slides are separated by a line of three or more hyphens (---).

---
title: Talk about slidown
output: talk.pptx
---

# First Slide

Content...
Frontmatter fields
  • title (string): The title of the presentation, written to the generated .pptx document properties (metadata).
  • output (string): Output .pptx path (used when --output is not given).
  • breaks (boolean): Control how single line breaks are rendered. Default (false) renders them as spaces; true preserves them as line breaks.
  • codeBlockToImageCommand (string): Command used to convert code blocks to images (see Code blocks to images).
  • defaults (array): Conditional page configuration using CEL expressions.
Markdown specification

slidown follows the same Markdown specification as deck: CommonMark plus selected GitHub Flavored Markdown extensions (tables, strikethrough), with a restricted set of raw inline HTML for text-level semantics (<mark>, <kbd>, <sub>, <sup>, <u>, …). Speaker notes are written as HTML comments (<!-- ... -->).

Within each slide, headings are mapped to placeholders by depth:

  • The shallowest heading on the slide → title
  • The next heading level → subtitle
  • Everything else → body

See docs/markdown.md for the full Markdown reference, including supported/unsupported features, inline-style mappings and line-break handling. A few edge behaviors (notably how content that overflows the available placeholders is handled) currently differ from deck and are not yet a stable contract; these are called out inline in that reference.

SVG images are, where possible, converted into native editable PowerPoint shapes; SVGs using features that cannot be faithfully converted fall back to a native SVG picture with a rasterized PNG fallback. SVGs that reference external or relative resources (which can't be packaged) instead get a best-effort PNG-only rendering. See Images for details.

Configuration file

slidown reads optional global configuration:

  1. ${XDG_CONFIG_HOME:-~/.config}/slidown/config-{profile}.yml (with --profile)
  2. ${XDG_CONFIG_HOME:-~/.config}/slidown/config.yml
breaks: true
codeBlockToImageCommand: "go run ./cmd/txt2img"
template: "theme.pptx"

Settings in frontmatter take precedence over the configuration file. The template field is honored only from the configuration file (or the --template flag) and only when creating a new output file; it cannot be set in a deck's frontmatter.

Code blocks to images

You can convert Markdown code blocks to images by specifying a command that outputs image data (PNG/JPEG/GIF) to standard output, or to a file via the {{output}} placeholder:

$ slidown apply deck.md --code-block-to-image-command "some-command"

This is useful for rendering diagrams (e.g. Mermaid) or syntax-highlighted code as images.

Status

slidown is under active development. apply already reuses unchanged whole slides; finer-grained intra-slide (sub-element) diffing is future work. See docs/design.md for the architecture and the incremental-rebuild design.

Acknowledgements

  • k1LoW/deckslidown is a sibling project of deck and reuses its Markdown parsing and content model. The Markdown specification, the element-to-slide mapping rules and much of the surrounding design come from deck. Many thanks to @k1LoW and the deck contributors.

License

MIT

Documentation

Index

Constants

View Source
const (
	Name    = "slidown"
	Version = "0.0.8"
)
View Source
const (
	// StyleDel is the style name applied to GFM strikethrough (`~~`) and the
	// HTML <del> element.
	StyleDel = "del"
)

StyleName constants for inline-syntax styles shared with the markdown parser.

Variables

View Source
var Revision = "HEAD"

Functions

This section is empty.

Types

type BlockQuote

type BlockQuote struct {
	Paragraphs []*Paragraph `json:"paragraphs,omitempty"`
	Nesting    int          `json:"nesting,omitempty"`
}

func (*BlockQuote) String

func (b *BlockQuote) String() string

type Body

type Body struct {
	Paragraphs []*Paragraph `json:"paragraphs,omitempty"`
}

Body represents the content body of a slide.

func (*Body) String

func (b *Body) String() string

type Bullet

type Bullet string

Bullet represents the type of bullet point for a paragraph.

const (
	BulletNone     Bullet = ""
	BulletDash     Bullet = "-"
	BulletNumbered Bullet = "1"
)

Bullet constants for different bullet point types.

type Fragment

type Fragment struct {
	Value     string `json:"value"`
	Bold      bool   `json:"bold,omitempty"`
	Italic    bool   `json:"italic,omitempty"`
	Link      string `json:"link,omitempty"`
	Code      bool   `json:"code,omitempty"`
	StyleName string `json:"style_name,omitempty"`
}

Fragment represents a text fragment within a paragraph.

func (*Fragment) StylesEqual

func (f *Fragment) StylesEqual(other *Fragment) bool

type Image

type Image struct {
	// contains filtered or unexported fields
}

func NewImage

func NewImage(pathOrURL string) (_ *Image, err error)

func NewImageFromCodeBlock

func NewImageFromCodeBlock(r io.Reader) (_ *Image, err error)

func NewImageFromMarkdown

func NewImageFromMarkdown(pathOrURL string) (_ *Image, err error)

func (*Image) Bytes

func (i *Image) Bytes() []byte

func (*Image) Checksum

func (i *Image) Checksum() uint32

func (*Image) Dimensions added in v0.0.8

func (i *Image) Dimensions() (w, h int, err error)

func (*Image) Image

func (i *Image) Image() (image.Image, error)

func (*Image) IsSVG added in v0.0.8

func (i *Image) IsSVG() bool

func (*Image) MarshalJSON

func (i *Image) MarshalJSON() (_ []byte, err error)

MarshalJSON and UnmarshalJSON are defined for cloning data and for similarity comparisons of `slide` structures.

func (*Image) PHash

func (i *Image) PHash() (_ *goimagehash.ImageHash, err error)

func (*Image) RasterPNG added in v0.0.8

func (i *Image) RasterPNG(scale float64) ([]byte, error)

RasterPNG renders the image to PNG bytes. The scale factor applies only to SVGs (rendered at scale × their intrinsic size); for raster inputs (PNG/JPEG/ GIF) scale is ignored and the source is re-encoded as PNG at its native size. For SVGs it is a best-effort raster produced by the pure-Go oksvg rasterizer, which does not support every SVG feature (notably filter, clipPath, mask, embedded <image>, foreignObject and <text>); such parts may be omitted. It is intended only as a compatibility fallback for viewers that can't render the embedded native SVG (which PowerPoint 2016+ uses as the primary rendering).

func (i *Image) SetLink(link string)

func (*Image) String

func (i *Image) String() string

func (*Image) UnmarshalJSON

func (i *Image) UnmarshalJSON(data []byte) (err error)

type MIMEType

type MIMEType string
const (
	MIMETypeImagePNG  MIMEType = "image/png"
	MIMETypeImageJPEG MIMEType = "image/jpeg"
	MIMETypeImageGIF  MIMEType = "image/gif"
	MIMETypeImageSVG  MIMEType = "image/svg+xml"
)

type Paragraph

type Paragraph struct {
	Fragments []*Fragment `json:"fragments,omitempty"`
	Bullet    Bullet      `json:"bullet,omitempty"`
	Nesting   int         `json:"nesting,omitempty"`
}

Paragraph represents a paragraph within a slide body.

func (*Paragraph) String

func (p *Paragraph) String() string

type Slide

type Slide struct {
	Layout string `json:"layout"`
	Freeze bool   `json:"freeze,omitempty"`
	Skip   bool   `json:"skip,omitempty"`
	// Key is an opaque, stable per-page identifier (from the markdown page
	// config) used to match a slide to its existing counterpart across inserts,
	// deletions and reordering during an incremental rebuild. It is identity,
	// not content, so it is excluded from the content fingerprint.
	Key string `json:"-"`
	// TitleSlot reports that the slide occupies the deck's title-layout slot
	// (the first rendered slide) under the built-in first-slide layout default.
	// It only matters when Layout is empty: it is folded into the content
	// fingerprint so a slide that moves into or out of the first position — and
	// therefore switches between the title and content default layout — is
	// re-rendered instead of being reused verbatim with a stale layout. It is
	// identity-adjacent, not authored content, so it is excluded from JSON.
	TitleSlot      bool          `json:"-"`
	Titles         []string      `json:"titles,omitempty"`
	TitleBodies    []*Body       `json:"title_bodies,omitempty"`
	Subtitles      []string      `json:"subtitles,omitempty"`
	SubtitleBodies []*Body       `json:"subtitle_bodies,omitempty"`
	Bodies         []*Body       `json:"bodies,omitempty"`
	Images         []*Image      `json:"images,omitempty"`
	BlockQuotes    []*BlockQuote `json:"block_quotes,omitempty"`
	Tables         []*Table      `json:"tables,omitempty"`
	SpeakerNote    string        `json:"speaker_note,omitempty"`
}

func (*Slide) Fingerprint

func (s *Slide) Fingerprint() string

Fingerprint returns the serialized slide signature embedded into the generated .pptx so an incremental rebuild can detect whether the source for a slide changed, without reverse-parsing the output.

Non-image content is captured by an exact hash, while images are recorded by their perceptual hash (falling back to a content checksum when a perceptual hash cannot be computed). Image comparison is order-independent and tolerant of recompression, so reordering or recompressing images on a slide does not force a regenerate.

func (*Slide) MatchesFingerprint

func (s *Slide) MatchesFingerprint(stored string) bool

MatchesFingerprint reports whether this slide's source is equivalent to the one captured by a previously embedded fingerprint: non-image content must match exactly and the images must match as an order-independent, perceptually compared set.

type Slides

type Slides []*Slide

type Table

type Table struct {
	Rows []*TableRow `json:"rows,omitempty"`
}

type TableCell

type TableCell struct {
	Fragments []*Fragment `json:"content,omitempty"`
	Alignment string      `json:"alignment,omitempty"`
	IsHeader  bool        `json:"is_header,omitempty"`
}

type TableRow

type TableRow struct {
	Cells []*TableCell `json:"cells,omitempty"`
}

Directories

Path Synopsis
cmd
slidown command
Package md provides functionality for parsing markdown into slides.
Package md provides functionality for parsing markdown into slides.
Package pptx provides a pure-Go writer for PowerPoint (.pptx / OOXML) presentations.
Package pptx provides a pure-Go writer for PowerPoint (.pptx / OOXML) presentations.
Package render maps slidown's internal slide model (the slidown package types produced by the md parser) onto the pptx package's serializable model.
Package render maps slidown's internal slide model (the slidown package types produced by the md parser) onto the pptx package's serializable model.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL