Lualatex package that allows to use markdown inside latex.
3
stars
33
commits
Lua
primary language
Sep 9, 2026
updated
Mark2TeX is a LuaLaTeX package for Markdown-like text fragments inside
LaTeX documents. It reads .md files during LaTeX compilation, translates
the currently supported dialect to LaTeX, and inputs the generated .tex
file back into the document.
The focus is on a LaTeX-friendly writing style for scientific documents, not on full Markdown or CommonMark compatibility. If compatibility with common Markdown tools happens as a side effect, that is welcome, but it is not the primary project goal.
Mark2TeX is useful when larger chunks of text are more convenient to write in a Markdown-like syntax, while the target document should still remain a real LaTeX document. LaTeX commands, mathematical expressions, citations, and raw LaTeX environments are therefore intentionally allowed inside the Markdown.
A typical snippet:
# Motivation
The calibration follows @calibration and uses $\alpha < 0.1$ as threshold.
For the final selection we use:
- **nominal** reconstruction
- systematic variations with \cref{sec:systematics}
- the response model from [@response; @detector]
```tex
\begin{align}
y &= f(x) \\
&= x^2 + \alpha
\end{align}
```
This turns into LaTeX output such as:
\section{Motivation}
The calibration follows \cite{calibration} and uses $\alpha < 0.1$ as threshold.
For the final selection we use:
\begin{itemize}
\item \textbf{nominal} reconstruction
\item systematic variations with \cref{sec:systematics}
\item the response model from \parencite{response, detector}
\end{itemize}
\begin{align}
y &= f(x) \\
&= x^2 + \alpha
\end{align}
The current parser intentionally supports only a small, tested subset:
#, ##, ###, ...*text* or _text_**text** or __text__~~text~~`code`tex blocks as raw LaTeX output>$...$ or \\(...\\)$$...$$ or \\[...\\]\alpha, \cref{...}, or \textit{...}-, *, or +1. or 1)@key[@key1; @key2], including locators such as [@key, p. 433]\begin{...} and \end{...}$$ ... $$ or \\[ ... \\] wrappersThe output is LaTeX-like:
\section, \subsection,
\subsubsection, \paragraph, and \subparagraph@key becomes \cite{key} by default[@key1; @key2] becomes \parencite{key1, key2} by default[@key, p. 433] becomes \parencite[p. 433]{key} by defaultitemize or enumeratequote environmentstabular environments; delimiter colons control l, c, and r alignmentverbatimtex code blocks are emitted unchanged as LaTeX$$...$$ is normalized to \\[...\\]; \\[...\\] is kept in that formMathematics is a Mark2TeX extension; CommonMark itself does not define math delimiters. The supported forms and their output are:
| Input | Context | Output |
|---|---|---|
$x^2$ | inline text, headings, lists, blockquotes, and table cells | unchanged |
\\(x^2\\) | inline text, headings, lists, blockquotes, and table cells | unchanged |
$$x^2$$ | display block, including lists and blockquotes | \\[x^2\\] |
\\[x^2\\] | display block, including lists and blockquotes | unchanged |
\\begin{align}...\\end{align} | raw LaTeX block | unchanged |
Markdown syntax inside math is not interpreted. A raw LaTeX environment wrapped
in $$...$$ or \\[...\\] is emitted as the environment alone, avoiding
invalid nested display math. Display delimiters in pipe-table cells are kept
literal and reported as warnings; inline math is supported there.
Unclosed, empty, or mismatched math delimiters are preserved literally and
produce a math-delimiter warning during file conversion. A delimiter
preceded by an odd number of backslashes is treated as escaped. Code spans,
fenced code blocks, and tex blocks are never parsed as math.
A blockquote is written by starting every quoted line with >:
> **Beobachtungen** → Daten → erkennbare Zusammenhänge → Modell → Anwendung auf neue Fälle
>
> Eine zweite Zeile mit *Inline-Formatierung*.
Mark2TeX removes the markers, applies the supported inline formatting, and
wraps the complete block in a LaTeX quote environment. Blank lines inside a
blockquote must therefore be written as > lines. Nested blockquotes and lazy
continuation lines without a > marker are not supported.
Mark2TeX only transforms complete, unambiguous Markdown constructs. An
underscore in ordinary text (for example snake_case) is kept literally;
italics require a matching closing underscore or asterisk. Likewise, an
unclosed fenced code block or an unclosed \begin{...} environment is emitted
as ordinary text instead of being partially converted. Paragraphs immediately
before or after a list remain separate paragraphs.
Mark2TeX is not a full Markdown converter. In particular, do not expect the following to work like they do in CommonMark, Pandoc, or GitHub Markdown:
If you need such constructs, the preferred approach at the moment is usually
direct LaTeX, for example as a tex code block or a raw LaTeX environment.
In a LuaLaTeX document, Mark2TeX is loaded as a package:
\documentclass{article}
\usepackage{mark2tex}
\begin{document}
\mdinput{content.md}
\end{document}
\mdinput{...} converts the given Markdown file to LaTeX and inputs the
generated file via \input. Generated files are placed in the mark2tex/
directory by default.
For chapter-like files there is also:
\mdinclude{chapter.md}
This inputs the generated file via \include.
Both \mdinput and \mdinclude register their Markdown source files with
LuaTeX's recorder. Build tools such as latexmk can therefore detect Markdown
changes from the generated .fls file and rebuild the document automatically.
Important: Mark2TeX requires LuaLaTeX. Other engines such as pdfLaTeX are not supported.
You can build a TeX Live package archive from this repository:
make dist
This produces dist/mark2tex.tar.xz. The archive contains a TDS structure and
an embedded tlpobj, so it can be installed directly with tlmgr:
tlmgr install --file dist/mark2tex.tar.xz
To check the archive without installing it, use:
make tlmgr-install-dry-run
The package installs mark2tex.sty under tex/latex/mark2tex/ and the Lua
implementation under scripts/mark2tex/.
Overleaf usually does not allow project-local installation via tlmgr.
For Overleaf there is therefore a separate bundle:
make overleaf-zip
This produces dist/mark2tex-overleaf.zip. That archive is not a TeX Live
installation; instead it contains the files in a form that can be uploaded
directly into an Overleaf project:
mark2tex.sty
mark2tex.lua
src/mark2tex/*.lua
README.md
LICENSE
In Overleaf, mark2tex.sty and mark2tex.lua must live at the top level of the
project; the src/mark2tex/ directory must remain relative to them. In the
Overleaf menu, the compiler must be set to LuaLaTeX. After that, the package
can be used like it is locally:
\usepackage{mark2tex}
\begin{document}
\mdinput{content.md}
\end{document}
If a mark2tex_config.lua file exists in the working directory, it is used when
the package is loaded. This lets you adjust the main LaTeX mappings:
return {
header = {
"chapter",
"section",
"subsection",
"subsubsection",
"paragraph",
},
citation = "autocite",
paren_citation = "parencite",
save_dir = "generated-mark2tex",
}
The default configuration is:
return {
header = {
"section",
"subsection",
"subsubsection",
"paragraph",
"subparagraph"
},
paren_citation = "parencite",
citation = "cite",
save_dir = "mark2tex",
}
Inline LaTeX is preserved:
The corrected energy is $E_\mathrm{corr}$ and the result is shown in
\cref{fig:energy-response}.
Citations can be written concisely:
The detector model follows @detector-note and the calibration strategy follows
[@calibration-paper; @run2-performance].
For a parenthetical citation with a page or section locator, place the locator
after a comma. It is passed as the optional argument of \parencite:
The original proposal is discussed in [@Turing1950, p. 433].
Pipe tables accept the usual alignment markers in their delimiter row and can contain the supported inline Markdown and LaTeX syntax:
| Quantity | Value | Comment |
| :------- | :---: | ------: |
| Energy | $E$ | **fit** |
| Events | 42 | @sample |
This produces a centered tabular with left-, center-, and right-aligned
columns respectively.
More complex LaTeX blocks can be written directly in Markdown:
```tex
\begin{table}[h]
\centering
\caption{Nominal binning}
\label{tab:binning}
\begin{tabular}{l|c}
\toprule
Layer & bins \\
\midrule
1 & 32 \\
\bottomrule
\end{tabular}
\end{table}
```
Raw environments without a code fence are also recognized:
\begin{align}
p(x) &= p(z)\left.\dv{f^{-1}}{x}\right|_{x=x_0}
\end{align}
Project layout:
src/mark2tex/ contains the Lua implementation.mark2tex.lua is the compatibility entry point.mark2tex.sty integrates Mark2TeX into LuaLaTeX.tests/ contains fixture tests, unit tests, and a LaTeX smoke test.scripts/ contains development helpers.Run regression tests:
make test
Run individual fixtures:
lua tests/run.lua tests/header.test tests/lists.test
Test LuaLaTeX integration:
make latex-smoke
The test runner uses luaunit if it is installed. If luaunit is missing, it
falls back to a small built-in assertion runner so that parser and writer
fixtures can still be checked.
At runtime, the project currently uses Lua modules for LPeg, filesystem access, and MD5:
lpeglfsmd5A working LuaLaTeX installation is required for the LaTeX integration.
luaunit is optional for tests.
33 commits
Hacker News (1)
Lua
89.2%
Makefile
5.5%
TeX
5.3%
Lualatex package that allows to use markdown inside latex.
3
stars
33
commits
Lua
primary language
Sep 9, 2026
updated
Mark2TeX is a LuaLaTeX package for Markdown-like text fragments inside
LaTeX documents. It reads .md files during LaTeX compilation, translates
the currently supported dialect to LaTeX, and inputs the generated .tex
file back into the document.
The focus is on a LaTeX-friendly writing style for scientific documents, not on full Markdown or CommonMark compatibility. If compatibility with common Markdown tools happens as a side effect, that is welcome, but it is not the primary project goal.
Mark2TeX is useful when larger chunks of text are more convenient to write in a Markdown-like syntax, while the target document should still remain a real LaTeX document. LaTeX commands, mathematical expressions, citations, and raw LaTeX environments are therefore intentionally allowed inside the Markdown.
A typical snippet:
# Motivation
The calibration follows @calibration and uses $\alpha < 0.1$ as threshold.
For the final selection we use:
- **nominal** reconstruction
- systematic variations with \cref{sec:systematics}
- the response model from [@response; @detector]
```tex
\begin{align}
y &= f(x) \\
&= x^2 + \alpha
\end{align}
```
This turns into LaTeX output such as:
\section{Motivation}
The calibration follows \cite{calibration} and uses $\alpha < 0.1$ as threshold.
For the final selection we use:
\begin{itemize}
\item \textbf{nominal} reconstruction
\item systematic variations with \cref{sec:systematics}
\item the response model from \parencite{response, detector}
\end{itemize}
\begin{align}
y &= f(x) \\
&= x^2 + \alpha
\end{align}
The current parser intentionally supports only a small, tested subset:
#, ##, ###, ...*text* or _text_**text** or __text__~~text~~`code`tex blocks as raw LaTeX output>$...$ or \\(...\\)$$...$$ or \\[...\\]\alpha, \cref{...}, or \textit{...}-, *, or +1. or 1)@key[@key1; @key2], including locators such as [@key, p. 433]\begin{...} and \end{...}$$ ... $$ or \\[ ... \\] wrappersThe output is LaTeX-like:
\section, \subsection,
\subsubsection, \paragraph, and \subparagraph@key becomes \cite{key} by default[@key1; @key2] becomes \parencite{key1, key2} by default[@key, p. 433] becomes \parencite[p. 433]{key} by defaultitemize or enumeratequote environmentstabular environments; delimiter colons control l, c, and r alignmentverbatimtex code blocks are emitted unchanged as LaTeX$$...$$ is normalized to \\[...\\]; \\[...\\] is kept in that formMathematics is a Mark2TeX extension; CommonMark itself does not define math delimiters. The supported forms and their output are:
| Input | Context | Output |
|---|---|---|
$x^2$ | inline text, headings, lists, blockquotes, and table cells | unchanged |
\\(x^2\\) | inline text, headings, lists, blockquotes, and table cells | unchanged |
$$x^2$$ | display block, including lists and blockquotes | \\[x^2\\] |
\\[x^2\\] | display block, including lists and blockquotes | unchanged |
\\begin{align}...\\end{align} | raw LaTeX block | unchanged |
Markdown syntax inside math is not interpreted. A raw LaTeX environment wrapped
in $$...$$ or \\[...\\] is emitted as the environment alone, avoiding
invalid nested display math. Display delimiters in pipe-table cells are kept
literal and reported as warnings; inline math is supported there.
Unclosed, empty, or mismatched math delimiters are preserved literally and
produce a math-delimiter warning during file conversion. A delimiter
preceded by an odd number of backslashes is treated as escaped. Code spans,
fenced code blocks, and tex blocks are never parsed as math.
A blockquote is written by starting every quoted line with >:
> **Beobachtungen** → Daten → erkennbare Zusammenhänge → Modell → Anwendung auf neue Fälle
>
> Eine zweite Zeile mit *Inline-Formatierung*.
Mark2TeX removes the markers, applies the supported inline formatting, and
wraps the complete block in a LaTeX quote environment. Blank lines inside a
blockquote must therefore be written as > lines. Nested blockquotes and lazy
continuation lines without a > marker are not supported.
Mark2TeX only transforms complete, unambiguous Markdown constructs. An
underscore in ordinary text (for example snake_case) is kept literally;
italics require a matching closing underscore or asterisk. Likewise, an
unclosed fenced code block or an unclosed \begin{...} environment is emitted
as ordinary text instead of being partially converted. Paragraphs immediately
before or after a list remain separate paragraphs.
Mark2TeX is not a full Markdown converter. In particular, do not expect the following to work like they do in CommonMark, Pandoc, or GitHub Markdown:
If you need such constructs, the preferred approach at the moment is usually
direct LaTeX, for example as a tex code block or a raw LaTeX environment.
In a LuaLaTeX document, Mark2TeX is loaded as a package:
\documentclass{article}
\usepackage{mark2tex}
\begin{document}
\mdinput{content.md}
\end{document}
\mdinput{...} converts the given Markdown file to LaTeX and inputs the
generated file via \input. Generated files are placed in the mark2tex/
directory by default.
For chapter-like files there is also:
\mdinclude{chapter.md}
This inputs the generated file via \include.
Both \mdinput and \mdinclude register their Markdown source files with
LuaTeX's recorder. Build tools such as latexmk can therefore detect Markdown
changes from the generated .fls file and rebuild the document automatically.
Important: Mark2TeX requires LuaLaTeX. Other engines such as pdfLaTeX are not supported.
You can build a TeX Live package archive from this repository:
make dist
This produces dist/mark2tex.tar.xz. The archive contains a TDS structure and
an embedded tlpobj, so it can be installed directly with tlmgr:
tlmgr install --file dist/mark2tex.tar.xz
To check the archive without installing it, use:
make tlmgr-install-dry-run
The package installs mark2tex.sty under tex/latex/mark2tex/ and the Lua
implementation under scripts/mark2tex/.
Overleaf usually does not allow project-local installation via tlmgr.
For Overleaf there is therefore a separate bundle:
make overleaf-zip
This produces dist/mark2tex-overleaf.zip. That archive is not a TeX Live
installation; instead it contains the files in a form that can be uploaded
directly into an Overleaf project:
mark2tex.sty
mark2tex.lua
src/mark2tex/*.lua
README.md
LICENSE
In Overleaf, mark2tex.sty and mark2tex.lua must live at the top level of the
project; the src/mark2tex/ directory must remain relative to them. In the
Overleaf menu, the compiler must be set to LuaLaTeX. After that, the package
can be used like it is locally:
\usepackage{mark2tex}
\begin{document}
\mdinput{content.md}
\end{document}
If a mark2tex_config.lua file exists in the working directory, it is used when
the package is loaded. This lets you adjust the main LaTeX mappings:
return {
header = {
"chapter",
"section",
"subsection",
"subsubsection",
"paragraph",
},
citation = "autocite",
paren_citation = "parencite",
save_dir = "generated-mark2tex",
}
The default configuration is:
return {
header = {
"section",
"subsection",
"subsubsection",
"paragraph",
"subparagraph"
},
paren_citation = "parencite",
citation = "cite",
save_dir = "mark2tex",
}
Inline LaTeX is preserved:
The corrected energy is $E_\mathrm{corr}$ and the result is shown in
\cref{fig:energy-response}.
Citations can be written concisely:
The detector model follows @detector-note and the calibration strategy follows
[@calibration-paper; @run2-performance].
For a parenthetical citation with a page or section locator, place the locator
after a comma. It is passed as the optional argument of \parencite:
The original proposal is discussed in [@Turing1950, p. 433].
Pipe tables accept the usual alignment markers in their delimiter row and can contain the supported inline Markdown and LaTeX syntax:
| Quantity | Value | Comment |
| :------- | :---: | ------: |
| Energy | $E$ | **fit** |
| Events | 42 | @sample |
This produces a centered tabular with left-, center-, and right-aligned
columns respectively.
More complex LaTeX blocks can be written directly in Markdown:
```tex
\begin{table}[h]
\centering
\caption{Nominal binning}
\label{tab:binning}
\begin{tabular}{l|c}
\toprule
Layer & bins \\
\midrule
1 & 32 \\
\bottomrule
\end{tabular}
\end{table}
```
Raw environments without a code fence are also recognized:
\begin{align}
p(x) &= p(z)\left.\dv{f^{-1}}{x}\right|_{x=x_0}
\end{align}
Project layout:
src/mark2tex/ contains the Lua implementation.mark2tex.lua is the compatibility entry point.mark2tex.sty integrates Mark2TeX into LuaLaTeX.tests/ contains fixture tests, unit tests, and a LaTeX smoke test.scripts/ contains development helpers.Run regression tests:
make test
Run individual fixtures:
lua tests/run.lua tests/header.test tests/lists.test
Test LuaLaTeX integration:
make latex-smoke
The test runner uses luaunit if it is installed. If luaunit is missing, it
falls back to a small built-in assertion runner so that parser and writer
fixtures can still be checked.
At runtime, the project currently uses Lua modules for LPeg, filesystem access, and MD5:
lpeglfsmd5A working LuaLaTeX installation is required for the LaTeX integration.
luaunit is optional for tests.
Hacker News (1)
33 commits
Lua
89.2%
Makefile
5.5%
TeX
5.3%