MARKTerm

CI

MARKTerm is a suite of tools to render Markdown anywhere, built on top of Markd:

It can also be used as a Crystal library.

Features

markterm on a light terminal markterm on a dark terminal

TODO

Done recently (markpdf):

Done (markterm):

Upstreaming

The forks and patches this suite carries are meant to shrink over time. These are the changes we want merged upstream; open pull requests are linked:

Usage as a program

Either get a static binary from the releases page or build from source:

This is the help:

Markterm - A tool to render markdown to the terminal

Usage:
  markterm <file> [-t <theme>][--code-theme <code-theme>][-l][-c][-w <width>][--hyphenate][--language <language>][--images|--no-images][--no-links][--no-pager]
  markterm -h | --help
  markterm --version

Options:
  -h --help                  Show this screen.
  -t <theme>                 Theme to use for coloring output
  --code-theme <code-theme>  Theme to use for coloring code blocks
  --version                  Show version.
  -l                         Force html-like links
  --no-links                 Never emit html-like links
  -c --color                 Force color output even when piping
  -w <width>                 Maximum line width for text wrapping (0 to disable, auto-detects if not specified)
  --hyphenate                Break long words at syllable boundaries when wrapping
  --language <language>      Hyphenation language: en or es [default: en]
  --images                   Force images where the terminal can show them
  --no-images                Never draw images; show placeholders instead
  --no-pager                 Never pipe output to $PAGER

If you use "-" as the file argument, markterm will read from stdin.

There is a similar markmark binary that will render markdown to markdown.

markpdf

The markpdf binary renders markdown to PDF. It converts the markdown to HTML with markd, lays it out with litehtml, and writes the PDF through libharu, via the C++ shim in ext/. Styling is CSS: markpdf ships a roster of built-in stylesheets (pick one with --style), and you can add your own rules on top with --css.

Markpdf - A tool to render markdown to PDF

  Usage:
    markpdf [<file>] [options]
    markpdf --list-styles
    markpdf -h | --help
    markpdf --version

Options:
  -h --help                  Show this screen.
  -t <theme>                 Theme to use for coloring output
  --code-theme <code-theme>  Theme to use for coloring code blocks
  --version                  Show version.
  -o <output>                Write the PDF to a file (defaults to standard output)
  --page-size <size>         Page size: a4 or letter [default: a4]
  --margin <margin>          Page margin in millimeters [default: 20]
  --style <style>            Built-in stylesheet setting layout and typography
                             (themes set colors instead): see --list-styles
                             [default: default]
  --list-styles              List the built-in stylesheets and exit
  --print-style              Print the built-in stylesheet named by --style to
                             standard output and exit
  --css <css>                Extra CSS file layered on top of the style; last
                             declaration wins (may be repeated)
  --pageless                 Single-page output: one page as tall as the document,
                             no headers/footers — good for on-screen reading,
                             wrong for printing. Very long documents scale down
                             to fit the PDF page-size limit.
  --font <font>              TTF font file to embed (can be repeated). Fonts are
                             matched by their internal family name; system fonts
                             are used automatically when available.
  --emoji-font <font>        TTF font used for emoji and symbols the main fonts
                             lack (auto-detected from system fonts by default)
    --header <header>          Page header text; "%p" is the page number, "%t" the
                               total page count. Split it with "|" into
                               left|center|right sections
    --footer <footer>          Page footer text; supports the same placeholders
                               and sections

If you use "-" as the file argument, markpdf will read from stdin.
Complete HTML documents (and .html files) are rendered directly,
skipping the markdown conversion.
Images are resolved relative to the input file's directory.

Styles

Built-in stylesheets set layout and typography; -t themes set colors. The rendered stylesheet is layered style → theme → --css, each later layer winning on equal specificity. --css may be repeated.

| style | look | |---------|-----------------------------------------------------| | default | clean sans-serif print style | | book | serif, justified, indented — long prose / e-readers | | dark | dark page, light text — screen reading | | sepia | warm paper tones, serif — e-reader default look |

See them, print one out, tweak it, and feed it back:

$ markpdf --list-styles
default  clean sans-serif print style (current)
book     serif, justified, indented — for long prose / e-readers
dark     dark page, light text — for screen reading
sepia    warm paper tones, serif — e-reader default look

$ markpdf --print-style --style book > my-book.css
$ $EDITOR my-book.css
$ markpdf book.md --style book --css my-book.css -o book.pdf

The dark style automatically uses a dark syntax-highlighting theme for code blocks unless you pass --code-theme or -t explicitly.

For on-screen reading, --pageless skips pagination entirely: the output is a single page as tall as the document (the --page-size still sets its width, --margin the outer whitespace). Headers, footers and page numbers do not apply in this mode. Documents longer than the PDF page-dimension limit (14,400 pt ≈ 20 printed pages) are scaled down uniformly — zoom in your viewer; text stays vector-crisp.

Math

Markdown math ($E = mc^2$ inline, $$…$$ display) is rendered as styled Unicode: italic serif with real sub/superscripts and LaTeX commands mapped to symbols (∫ ∑ ∞ π ± ≤ …). For display math you can get true text-art rendering (integral signs with limits, fraction bars) by enabling the optional GPL-3 libtexprintf library — it lives in ext/libtexprintf as a git submodule:

git submodule update --init ext/libtexprintf
make -C ext WITH_TEXMATH=1
WITH_TEXMATH=1 shards build

Note the license trade-off: libtexprintf is GPL-3, and statically linking it makes the resulting markpdf binary effectively GPL-3. The default build does not use it, keeps your existing license, and renders math with the Unicode styling pass.

Text uses embedded TrueType fonts with full Unicode support: the shim matches the CSS font-family names against the fonts you pass with --font and against installed system fonts (/usr/share/fonts, ~/.fonts, ...), falling back to the PDF base-14 fonts for Latin text when nothing matches.

Complete HTML documents are detected automatically and rendered directly — no markdown conversion — so markpdf doubles as a small HTML→PDF converter for the HTML subset litehtml supports.

Links pointing at http(s):// or mailto: URIs become clickable PDF link annotations, and internal anchors (including footnote references and their back-links) jump to their targets. Fenced code blocks get tartrazine syntax highlighting (the docopt lexer included).

Example with a dark base16 theme, page numbers and a header:

markpdf notes.md -o notes.pdf -t "0x96f" --header "notes" --footer "%p / %t"

Building markpdf from source requires libharu (pacman -S libharu, apt install libharu-dev, ...) and a C++ toolchain: run make -C ext once to build the shim, then shards build. See BUILDING.md for all build modes — including the GPL-3 math build and the license-clean alternative — plus tests and static release binaries.

markpdf-web

bin/markpdf-web (built alongside the other binaries) serves a small Kemal site that doubles as markpdf's playground and landing page: visitors edit markdown, tweak the styling knobs (style, themes, page size, margins, headers, pageless, custom CSS), and see the PDF update live.

bin/markpdf-web            # listens on :3000
PORT=8080 bin/markpdf-web  # or wherever you like

Documents live in the visitor's browser (localStorage) and in shareable #d=... URLs. Submitted markdown is uploaded to the server, rendered in memory, and streamed back with the response — no PDF file is ever written (only images that need converting pass through a private temp directory, deleted right after the render). Renders are serialized through a mutex because the PDF shim is not known to be thread-safe, so it is meant for demo traffic, not for heavy use.

Limits (all env-tunable) keep the demo from being set on fire:

| Variable | Default | Meaning | |----------|---------|---------| | MARKPDF_WEB_MAX_MARKDOWN_KB | 512 | Largest accepted markdown body | | MARKPDF_WEB_MAX_RENDER_SECONDS | 30 | Render timeout, answers 503 | | MARKPDF_WEB_MAX_QUEUE | 8 | Waiting renders before answers turn 429 | | MARKPDF_WEB_MAX_RENDERS_PER_MINUTE | 30 | Renders per minute per client IP | | MARKPDF_WEB_FETCH_IMAGES | off | Set to 1 to fetch remote images | | MARKPDF_MAX_IMAGE_MB | 8 | Largest remote image body fetched |

Remote image fetching is off on the demo — a markdown file must not make the server talk to the network. Re-enable it with MARKPDF_WEB_FETCH_IMAGES=1; when it is on, images are only fetched from public http(s) hosts — loopback, private ranges and link-local addresses (cloud metadata included) are refused, redirects included — and anything bigger than the image cap is dropped. The CLI fetches by default and can skip remote images with --no-remote-images. Local/relative image sources resolve against an empty directory, so server files are never reachable as "images". Temp PDFs are deleted the moment their bytes are read, and a sweeper removes crash orphans older than an hour every ten minutes.

Or run it from Docker (multi-stage build on alpine:edge, runtime ships DejaVu/Noto fonts and runs as an unprivileged user):

docker build -f Dockerfile.web -t markpdf-web .
docker run -p 3000:3000 markpdf-web

Usage as a library

  1. Add the dependency to your shard.yml:

    dependencies:
      markterm:
        github: ralsina/markterm

In your code, use it like this:

  puts Markd.to_term(source)
  puts Markd.to_md(source)

The PDF side is a library too: Markd::Pdf::Renderer owns every option as instance state, so instances are independent and reusable — no global style accumulates between renders.

  require "markterm/pdf"

  renderer = Markd::Pdf::Renderer.new(style: "book",
    header: "notes", footer: "%p / %t")
  renderer.add_css(File.read("my-book.css"))
  pages = renderer.render(source, "book.pdf")

  # or one-shot, no instance to keep:
  Markd::Pdf.render(source, "out.pdf", style: "dark")

The only process-wide state is the font cache (Markd::Pdf.register_font, Markd::Pdf.emoji_font=): parsed fonts are cached for the life of the process because font metadata parsing is expensive, and duplicate registrations are ignored.

Renderers are not thread-safe: litehtml and libharu make no thread-safety claims either. If several threads need to render, serialize the renders or use one process per worker.

Contributing

  1. Fork it (https://github.com/ralsina/markterm/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors