# MarkdownConverter — Monospace Font Embedding Design **Date:** 2026-06-30 **Status:** Approved **Author:** Amit Haridas ## Overview Guarantee proper ASCII character alignment in MarkdownConverter's preview and every supported export format (PDF, DOCX, HTML, plus ODT/RTF/EPUB/LaTeX), with no OS-level font dependency. The user can pick between **JetBrains Mono** (default) and **Fira Code**, and toggle ligatures (default off). Both font families are bundled inside the app, so alignment holds on every supported platform (Windows/macOS/Linux) without an internet connection, without system-wide font installation, and without the user touching anything system-level. ## Goals 1. ASCII art and code-block tables (e.g. `+----+----+` column delimiters) render at identical advance widths in the live preview **and** in every exported file. 2. No OS font dependency — TTFs ship inside the app. 3. Per-user choice of monospace family + ligature behaviour. 4. Self-contained exports: the exported PDF/DOCX/HTML file is portable to another machine and stays aligned. ## Non-Goals (v1) - No font subsetting of TTFs (we ship the full file; xelatex subsets it into the PDF automatically; DOCX carriers get the full TTF in `word/fonts/`). - No support for the user adding a third bundled font family. - No PPTX or revealjs/Beamer ligature-toggle (PPTX is a slide format that rarely carries ASCII tables; revealjs/Beamer inherit HTML/LaTeX defaults). - No licensed/commercial fonts (Fira Code and JetBrains Mono are both SIL OFL — confirmed in `assets/fonts/JetBrainsMono-LICENSE.txt`). ## Decisions Locked | Decision | Choice | Reason | |---|---|---| | Scope | All export formats | All-surfaces alignment | | DOCX strategy | Embed TTF into the DOCX zip | Truly portable; alignment holds even when the recipient lacks the font | | PDF strategy | Pass TTF path to xelatex via `fontspec` (`\setmonofont[Path=...]`) | xelatex subsets the font into the PDF; reproducible across machines | | Font choice | Both bundled; user-pickable in settings | Cover both preferences; default JetBrains Mono | | Ligatures | Off by default; user toggle | Ligatures change advance widths and break ASCII grid alignment | | License | SIL OFL (both families) | Embedding/bundling explicitly permitted when license travels with the binary (already present at `assets/fonts/JetBrainsMono-LICENSE.txt`) | ## Architecture ``` settings.json │ │ (renderer + main read on init) ▼ CSS body classes: .mono-jetbrains / .mono-fira .mono-ligatures-on / .mono-ligatures-off --font-mono-active token ▲ ▲ │ IPC │ IPC │ │ Preview pane ┌───────────┴──────────┐ ASCII Generator window │ Export pipeline │ Print-preview iframe │ (main process) │ │ uses MonospaceFontConfig └──────────────────────┘ │ ▼ Bundle: assets/fonts/ ├─ JetBrainsMono-Regular.ttf ├─ JetBrainsMono-Bold.ttf ├─ FiraCode-Regular.ttf ├─ FiraCode-Bold.ttf └─ (existing woff2 for renderer) ``` ## New Modules | File | Role | |---|---| | `src/main/MonospaceFontConfig.js` | Single source of truth. Resolves the active monospace family + weight → absolute TTF path, with awareness of dev vs packaged (asar.unpacked) layout. Returns `null` + warns when a file is missing. | | `src/main/PdfFontHeader.js` | Builds the xelatex `header.tex` snippet with `\usepackage{fontspec}\setmonofont{...ttf}[Path=...,UprightFont=*-Regular,BoldFont=*-Bold,Ligatures=NoCommon/TeX]`. Also returns the lualatex equivalent. | | `src/main/DocxFontEmbedder.js` | Unzips a pandoc-produced DOCX with `jszip`, writes TTFs into `word/fonts/`, patches `[Content_Types].xml`, `_rels/document.xml.rels`, creates `word/fontTable.xml` with `` referencing the TTF, patches `word/styles.xml` so the `SourceCode`/`VerbatimChar` styles bind to the embedded font name, then rezips. Idempotent. | | `src/main/EpubFontEmbedder.js` | Wrapper around pandoc `--epub-embed-font` — verifies the chosen TTF is referenced in `OEBPS/content.opf`; patches the manifest if missing. | | `src/main/ExportCss.js` | Returns a self-contained CSS string with `@font-face { src: url(data:font/woff2;base64,...) }` for the chosen family. Used by HTML export `--css` and by print-preview iframe. | | `src/main/settings/SettingsUI.Monospace.js` | Two new controls in the in-app settings dialog: monospace font select + ligatures checkbox. Persists to `/settings.json`. | ## Modified Modules | File | Change | |---|---| | `src/fonts.css` | Add `@font-face` entries for Fira Code Regular (400) and Bold (700), pointing to `assets/fonts/FiraCode-*.woff2` (downloaded via the extended `download-tools.js`). | | `src/styles/tokens.css` + `src/styles-concreteinfo.css` | Define new tokens `--font-mono-active` (resolves to `"JetBrains Mono"` or `"Fira Code"`) and `--font-mono-feature` (`"liga" 0, "calt" 0, "dlig" 0` for ligatures-off, else `normal`). Body classes flip these. | | `src/styles-modern.css` | `.editor-textarea`, `.preview-content code`, `.preview-content pre`, `.codemirror-container .cm-editor` reference `var(--font-mono-active)` and apply `font-feature-settings: var(--font-mono-feature)`. | | `src/ascii-generator.html` | Replace `` with `` plus inline `body` class defaulting. The window is plain HTML; the renderer script that opens it sets `body.classList` from settings. | | `src/print-preview.js` | Inject `