From 5178d9118725da9b347c02b5fc4e527effb00008 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Tue, 30 Jun 2026 23:18:15 +0530 Subject: [PATCH] docs(monospace): approve design for embedded monospace font in preview + all exports Bundles JetBrains Mono + Fira Code TTFs in assets/fonts/. Embeds them into DOCX (jszip), passes path via xelatex fontspec for PDF, base64-injects @font-face for HTML/EPUB. Replaces Consolas (Windows-only) and Google Fonts CDN load in the ASCII generator window. Adds user-pickable monospace family + ligature toggle (default JBM, no ligatures) for ASCII column alignment. Closes: N/A Refs: docs/superpowers/specs/2026-06-30-monospace-font-embedding-design.md Amit Haridas --- ...6-06-30-monospace-font-embedding-design.md | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-30-monospace-font-embedding-design.md diff --git a/docs/superpowers/specs/2026-06-30-monospace-font-embedding-design.md b/docs/superpowers/specs/2026-06-30-monospace-font-embedding-design.md new file mode 100644 index 0000000..1bd3a44 --- /dev/null +++ b/docs/superpowers/specs/2026-06-30-monospace-font-embedding-design.md @@ -0,0 +1,216 @@ +# 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 `