#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ md2html_mathjax.py ------------------ Convert a Markdown file (with LaTeX math) into a standalone HTML file. - Inserts MathJax (CDN) and CSS into - Uses Python-Markdown + pymdownx.arithmatex so math survives Markdown rendering - Default inline CSS (dark-mode aware), overridable with --css-file or --no-css Requirements: pip install markdown pymdown-extensions Usage: python md2html_mathjax.py input.md -o output.html # Options: python md2html_mathjax.py input.md -o output.html \ --title "My Notes" \ --lang ja \ --mathjax-url https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js \ --css-file style.css License: MIT Author: ChatGPT """ import argparse import re from pathlib import Path from typing import Optional import markdown DEFAULT_CSS = r""" /* Minimal typesetting */ :root{--fg:#111;--bg:#fff;--muted:#666;--code-bg:#f6f8fa;--border:#e5e7eb;--link:#2563eb;} @media (prefers-color-scheme: dark){ :root{--fg:#e5e7eb;--bg:#0b121b;--muted:#9ca3af;--code-bg:#111827;--border:#1f2937;--link:#60a5fa;} } html {font-size: 16px;} body {margin: 2rem auto; max-width: 880px; padding: 0 1rem; color: var(--fg); background: var(--bg); line-height: 1.75; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans JP', 'Hiragino Sans', 'Yu Gothic', 'Helvetica Neue', Arial, 'Apple Color Emoji','Segoe UI Emoji';} h1,h2,h3,h4{line-height:1.3} pre, code {font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', 'Courier New', monospace;} pre {background: var(--code-bg); padding: 1rem; overflow:auto; border-radius: 8px; border:1px solid var(--border);} code {background: var(--code-bg); padding: .15rem .35rem; border-radius: 6px;} a {color: var(--link); text-decoration: none;} a:hover {text-decoration: underline;} table {border-collapse: collapse; width: 100%; margin: 1rem 0} th, td {border: 1px solid var(--border); padding: .5rem .75rem; text-align: left;} blockquote {border-left: 4px solid var(--border); margin: 1rem 0; padding: .25rem 1rem; color: var(--muted);} hr {border: 0; border-top:1px solid var(--border); margin: 2rem 0;} /* Ensure math spans render inline nicely */ .arithmatex { font-size: 1em; } """ HTML_TEMPLATE = """ {title} {css_block} {body} """ def derive_title(md_text: str, fallback: str) -> str: # 1) YAML metadata title: `title: ...` (very loose) m = re.search(r'(?mi)^title:\s*(.+)$', md_text) if m: return m.group(1).strip() # 2) First ATX heading (# H1) m = re.search(r'(?m)^#\s+(.+?)\s*$', md_text) if m: return m.group(1).strip() return fallback def build_html(md_text: str, title: str, lang: str, mathjax_url: str, css_text: Optional[str]) -> str: # Render Markdown with math passthrough using pymdownx.arithmatex exts = [ "extra", # tables, etc. "toc", # optional TOC ids "sane_lists", "attr_list", "pymdownx.arithmatex", "codehilite", # highlight wrapper (no pygments by default) ] ext_cfg = { "pymdownx.arithmatex": {"generic": True}, "codehilite": {"guess_lang": False, "use_pygments": False}, "toc": {"permalink": True}, } body = markdown.markdown(md_text, extensions=exts, extension_configs=ext_cfg, output_format="xhtml") css_block = f"" if css_text else "" return HTML_TEMPLATE.format(lang=lang, title=title, css_block=css_block, mathjax_url=mathjax_url, body=body) def main(): ap = argparse.ArgumentParser(description="Markdown (with LaTeX) -> HTML with MathJax & inline CSS.") ap.add_argument("input_md", help="Input Markdown file path") ap.add_argument("-o", "--output", required=True, help="Output HTML file path") ap.add_argument("--title", default=None, help="HTML . Default: from Markdown H1 or file name") ap.add_argument("--lang", default="ja", help="HTML lang attribute (default: ja)") ap.add_argument("--mathjax-url", default="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js", help="MathJax v3 bundle URL (default: tex-chtml.js)") ap.add_argument("--css-file", default=None, help="Use CSS from file instead of the built-in default") ap.add_argument("--no-css", action="store_true", help="Do not embed any CSS") args = ap.parse_args() inp = Path(args.input_md).expanduser().resolve() out = Path(args.output).expanduser().resolve() if not inp.exists(): raise SystemExit(f"[ERR] Markdown not found: {inp}") md_text = inp.read_text(encoding="utf-8") title = args.title or derive_title(md_text, inp.stem) css_text = None if not args.no_css: if args.css_file: css_text = Path(args.css_file).read_text(encoding="utf-8") else: css_text = DEFAULT_CSS html = build_html(md_text, title=title, lang=args.lang, mathjax_url=args.mathjax_url, css_text=css_text) out.parent.mkdir(parents=True, exist_ok=True) out.write_text(html, encoding="utf-8") print(f"[OK] Wrote: {out}") if __name__ == "__main__": main()