Skip to content

Releases: eagredev/inkmd

inkmd 0.5.0

10 Jun 16:30

Choose a tag to compare

The author can shape the page. Margins, font size, line spacing, page size and orientation become public API, forced page breaks land, and wide tables now fit the page losslessly by default. The four-layer pipeline and the determinism property are unchanged, and the defaults reproduce 0.4.0's output exactly: a document whose tables fit the page renders byte-identically at the defaults. Only tables that previously could not fit render differently (see Changed).

Added

  • LayoutConfig and flat layout overrides. A frozen LayoutConfig dataclass groups the layout knobs (page_size, margin, font_size, line_spacing, orientation, table_overflow, table_panel_min_chars), and every knob is also a plain keyword argument on compile() and render_file(). Both forms are permanent peers; when both are given, the flat argument wins, so a shared house-style config can be overridden per call.
  • Configurable margins, body font size, and line spacing. The whole type scale follows the body size: headings, list markers, table cells, and code all scale proportionally. Line spacing applies to prose, tables, and code alike.
  • More page sizes, custom dimensions, and landscape. legal, tabloid, a3, and a5 join letter and a4 (names now case-insensitive), page_size also accepts a custom (width, height) tuple in points, and orientation="landscape" swaps any size. The CLI --page-size flag gains the new names.
  • Forced page breaks via the standard CSS break div: <div style="page-break-after: always"></div> (and the break-after: page synonym) starts a new page. The pattern is the one browsers, Pandoc, and print stylesheets honor, and GitHub renders it as nothing, so a document carrying it stays portable. --- remains a thematic break. Leading, trailing, and doubled break divs do not produce blank pages.
  • Lossless wide-table fitting, the new default (table_overflow="wrap"). A table wider than the page first shrinks its columns to a readable floor and wraps cell text; a table that cannot fit even then splits into column panels, each repeating the first column as the key and marked "(continued)". table_panel_min_chars (default 8) sets the readable floor. The other modes: "shrink" (the pre-0.5 squeeze), "warn" (shrink plus a TableOverflowWarning), and "error" (raise TableOverflowError, for CI gates). Both classes are exported. A table that fits renders byte-identically in all four modes.
  • Page-tall table content is sliced, not lost. A row group taller than one page is split across pages at line boundaries, and a header taller than the usable page renders once instead of repeating, so every cell's text stays on a visible page. Wrap mode also guarantees a table never draws past the right margin.

Changed

  • Default behavior for tables too wide for the page. Previously columns squeezed toward minimum widths and a table with too many columns overflowed the right page edge, clipping content. Under the new wrap default that content now wraps or panels and stays visible. The pre-0.5 output remains available verbatim via table_overflow="shrink".

Parsing is untouched: conformance is unchanged at CommonMark 652/652 (100%) and the GFM extensions unchanged. 1177 tests across 49 files.


Install: pip install inkmd==0.5.0

Single-file zipapp: inkmd.pyz is attached below; it runs anywhere Python 3.9+ is available, no pip required. The zipapp omits the bundled emoji and text fonts, so emoji take the fallback path and non-Latin scripts render as [U+XXXX] markers there; use the pip install when you need them.

inkmd 0.4.0

08 Jun 17:57

Choose a tag to compare

Non-Latin text renders. inkmd now embeds a font for text the 14 base PDF fonts cannot represent, so Cyrillic, Greek, and Latin-Extended scripts render as real glyphs instead of falling back to ?. The 0.3.0 entry promised this ("Still ahead: text-font embedding for non-Latin scripts"); 0.4 delivers it. The four-layer pipeline, public API, and determinism property are unchanged: the same input still produces byte-identical output.

Added

  • Embedded TrueType font support. A TrueType (glyf-flavoured) font is embedded as a CID-keyed Type0/CIDFontType2 with Identity-H encoding, with a /ToUnicode map so the embedded text stays selectable and copyable. Non-WinAnsi text routes to the embedded font automatically while ASCII/Latin-1 stays on the base-14 family.
  • A bundled DejaVuSans font, shipped in the pip wheel, covering Cyrillic, Greek, and Latin-Extended. It loads with no system-font lookup, so output is reproducible wherever inkmd is installed.
  • A font_path= option on compile and render_file to embed a different TrueType font in place of the bundled one. A missing, unreadable, or unsupported font raises a clear TrueTypeFontError rather than a raw parse traceback.
  • A visible [U+XXXX] marker for any codepoint no available font can draw, replacing the old silent ?. inkmd also raises one MissingGlyphWarning per compile naming the missing codepoints, so the condition is machine-detectable and filterable.
  • Unicode NFC normalization at ingestion. The markdown source is normalized to NFC before parsing, so a decomposed sequence (NFD cafe followed by a combining acute accent) composes to its single codepoint and renders, instead of dropping the combining mark.

Known limitations

  • CJK is not rendered in this release. The bundled DejaVuSans has no CJK glyphs, so CJK codepoints show the [U+XXXX] marker. A CJK font pack is planned for a later release.
  • Non-Latin text inside table cells still renders ?. Table column widths are computed before the run split, so embedded-font routing does not yet reach table cells. The fix is pending; the same text in prose renders through the embedded font.

This release is rendering, not parsing: conformance is unchanged at CommonMark 652/652 (100%) and the GFM extensions unchanged.

inkmd 0.3.0

06 Jun 23:04

Choose a tag to compare

inkmd 0.3.0 takes spec conformance to 100%: CommonMark 0.31.2 at 652/652 and the GFM extension suite at 28/28, up from 85.0% and 71.4% in the 0.2.x line. The work centred on the block container model: per-item content columns, lazy-continuation reparse, tab virtual-columns, the four-space marker rule, block-level raw HTML passthrough, and a unified code-block newline convention.

Install: pip install inkmd==0.3.0

Single-file zipapp (font-less lite tier, emoji fall back to text): inkmd.pyz attached below.

Full notes: see CHANGELOG.md.

inkmd 0.2.1

02 Jun 00:39

Choose a tag to compare

See CHANGELOG.md [0.2.1] — benchmark
claims re-verified and corrected post-emoji; deterministic zipapp; adversarial security tests; cross-Python
3.9-3.13.

inkmd 0.2.0

31 May 00:18

Choose a tag to compare

Markdown to PDF, pure Python, zero system dependencies. MIT-licensed. Byte-deterministic output.

pip install inkmd — no Chrome, no wkhtmltopdf, no Pango/cairo, no LaTeX. Runs anywhere Python runs.

Highlights in 0.2.0

  • Color emoji render as inline color glyphs from a bundled font (Noto Color Emoji, SIL OFL 1.1) — single emoji, flags, skin tones, ZWJ sequences (families, the rainbow flag), keycaps — inline, in headings, and in table cells. Via a hand-rolled OpenType reader (no fonttools).
  • Image embedding — PNG (grayscale, RGB, indexed/palette with tRNS transparency) and JPEG. HTML <img> is supported too (honouring width/align), so the GitHub-README hero idiom renders. inkmd now renders its own README in full.
  • Tables split across pages — a table taller than a page breaks at a row boundary and continues with the header repeated, instead of overflowing off the bottom.
  • Reference links/images, hard breaks, indented code in lists, task lists, blockquote lazy continuation, multi-backtick code spans, HTML5 entities, and an inline-HTML allow-list.
  • Robustness — two adversarial render audits resolved (malformed-PNG crash, emoji-in-code, soft hyphen, ZWJ-leak, table/list overflow, and more).

Conformance

  • CommonMark 0.31.2: 554/652 (85.0%)
  • GFM extensions: 20/28 (71.4%)
  • 788 unit tests. Full per-section breakdown in docs/conformance.md.

Install

pip install inkmd

Or grab the single-file zipapp below (inkmd.pyz, ~440 KB, no install needed — Python 3.9+). The zipapp is the font-less "lite" build: emoji fall back to readable [name] labels rather than color glyphs. The pip wheel bundles the font and renders emoji in color.

See CHANGELOG.md for the full list.

inkmd 0.1.0

10 Jun 18:33

Choose a tag to compare

First public release of inkmd, a pure-Python markdown-to-PDF compiler with zero system dependencies, MIT-licensed, byte-deterministic output.

Highlights

  • Full CommonMark plus GFM extensions: pipe tables, autolinks, strikethrough, fenced code with language tags.
  • Library API (inkmd.compile, inkmd.render_file) and CLI (inkmd, inkmd.pyz).
  • Helvetica and Times font families with AFM-correct kerning emitted via TJ arrays.
  • Byte-identical PDF output for the same input on every platform, verified across pip install, python -m inkmd.cli, and the zipapp.
  • 501 tests, end-to-end PDF validity verified via qpdf --check.
  • Around 3,500 lines of pure-Python logic, no runtime dependencies.

Install

pip install inkmd
inkmd in.md -o out.pdf

Or download the single-file zipapp attached to this release (no pip needed):

curl -L -o inkmd.pyz https://github.com/eagredev/inkmd/releases/download/v0.1.0/inkmd.pyz
python inkmd.pyz in.md -o out.pdf

What is not in v0.1 yet

Documented limitations queued for v0.2:

  • Codepoints outside WinAnsi (CJK, Cyrillic, emoji, the rightwards arrow) render as ?. TTF font embedding lands in v0.2.
  • Images, task lists, tables-across-pages, tables-in-blockquotes, headers/footers/page numbers are all v0.2.

See CHANGELOG.md for the full feature list and docs/internals.md for a technical walk-through.