package pgn_to_tex
Install
dune-project
Dependency
Authors
Maintainers
Sources
md5=a280c5fe01593e4f8421b22cba6b2e6e
sha512=bab787485725decb030a527d05c74d3cc30ad1cca31a37e2c2a5358a58f82383e0b1ffb4f9dd9035159f3f4887f0ae909abcbdbd1ab119e1b1b2a2af621ef047
Description
Convert PGN files to LaTeX markdown, including rendered images of selected positions
README
Pgn_to_tex
Pgn_to_tex converts chess games written in PGN to LaTeX. It renders move numbers, moves, comments, variations, NAG annotations, clock times, and optional chessboard diagrams of selected positions.
Requirements
- OCaml 5.4 or newer
- dune
- A LaTeX installation with
xskakandmulticol(see LaTeX setup)
Installation
Install the released package with opam:
opam install pgn_to_texTo use the current repository instead, clone it and pin the checkout:
git clone https://github.com/TheRealOwenRees/pgn_to_tex.git
cd pgn_to_tex
opam install .Then add the library to the relevant dune file:
(libraries pgn_to_tex)Quick start
Open Pgn_to_tex and call to_tex with a PGN string:
let pgn = "1. e4 e5 2. Nf3 Nc6 3. Bb5"
let tex = Pgn_to_tex.to_tex pgnto_tex returns a LaTeX fragment without a preamble or \begin{document}. ~diagram_data defaults to no diagrams and ~clock defaults to false, so this basic call needs no other arguments.
To render diagrams, describe the selected positions as JSON and parse it with parse_diagrams_json:
let diagrams =
Pgn_to_tex.parse_diagrams_json
{| [
{"fen": "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1",
"path": ["d4"]},
{"fen": "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2",
"ply": 2}
] |}
let tex = Pgn_to_tex.to_tex ~diagram_data:diagrams pgnTo explicitly request a game with no diagrams, pass ~diagram_data:Pgn_to_tex.empty_diagrams.
diagram_data is abstract. Construct it with parse_diagrams_json or empty_diagrams instead of building the internal representation directly.
Diagram JSON schema
parse_diagrams_json accepts a JSON array. Each entry is an object:
[
{"fen": "8/8/8/8/8/8/4K3/7k w - - 0 1", "path": ["d4", "d5", "e4", "d6"]},
{"fen": "8/8/8/8/8/8/4K3/7k w - - 0 1", "ply": 5},
{"fen": "8/8/8/8/8/8/4K3/7k w - - 0 1", "ply": 9, "path": ["e4", "e5", "Nf3", "Nc6"]}
]Field | Type | Required | Description |
|---|---|---|---|
| string | yes | The board position to render. |
| array of string | one of path/ply | The SAN move sequence from the start of the game, including moves inside a variation (for example |
| integer | one of path/ply | Legacy half-move count from the start of the game: 1 after White's first move. It is used for mainline positions. |
Resolution rules:
- An entry may carry
path,ply, or both. - A matching
pathtakes precedence over a matchingply. - If no
pathmatches, mainline positions fall back to a matching legacyply. - Variation diagrams require
path; aply-only entry only matches mainline positions. - Missing both
pathandplyleaves the entry with no position to match.
Diagrams on the mainline use the default board size. Diagrams inside a variation are rendered inline and use the smaller smallboard size.
Malformed diagram JSON
If the JSON is invalid, or any entry is malformed, parse_diagrams_json returns empty diagram data instead of raising. Conversion then proceeds without diagrams.
Clocks
Clock annotations are opt-in:
let tex = Pgn_to_tex.to_tex ~clock:true ~diagram_data:diagrams pgnWith ~clock:true, the most recent clock values for both players are shown above and below each rendered diagram. Clock values come from PGN clock annotations such as {[%clk 0:10:00]} or an inline [%clk 0:10:00].
Clocks are displayed as part of diagram output. When no diagram is selected for a position, its clock values are not emitted.
Variations
Parenthesised variations are rendered inline inside the mainline, for example ( 2... d6 ). Mainline moves and move numbers are bold; variation moves are not. Comments and NAGs inside a variation are rendered there too. Variation diagrams are looked up by their full path and use smallboard so they do not dominate the subline.
Supported headers
The following PGN headers are rendered:
Eventis used as the title whenTitleis absent.Titleis the document title. WhenSubtitleis also present, the subtitle is rendered below the title. A title without a subtitle renders no subtitle block.Authoris the author line when present. Otherwise the author line is built fromWhiteandBlack, includingWhiteEloandBlackElowhen supplied.DateandSiteare combined into the date line. An empty date with a site renders the site; an empty date and site render an empty date.Resultis rendered in bold at the end of the game.
All other headers are ignored.
NAG annotations
The following Numeric Annotation Glyphs are rendered:
NAG | LaTeX |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Unmapped NAGs are ignored.
Error behavior
to_texraisesFailure "Parse error"when the PGN cannot be parsed.parse_diagrams_jsonnever raises for malformed JSON; it returns empty diagram data, as described above.
LaTeX setup
The generated string is a fragment: it does not include a document class, preamble, or \begin{document}. A complete document using xskak (which loads chessboard, skak, and the chess symbol fonts), multicol for the column layout, and the default diagram commands looks like this:
\documentclass{article}
\usepackage{xskak}
\usepackage{multicol}
\usepackage{amsmath,amssymb}
% NAG $13 renders \infty, a math-mode symbol, in text mode.
\let\pgninfinity\infty
\renewcommand{\infty}{\ensuremath{\pgninfinity}}
\begin{document}
\begin{multicols}{2}
% Paste the string returned by Pgn_to_tex.to_tex here.
\end{multicols}
\end{document}The relevant packages are:
xskakfor\newchessgame,\chessboard, and the NAG symbol commands.multicolfor the two-column environment shown above.amsmathandamssymbfor math symbols.
JavaScript build
The JavaScript target is optional. Dune skips it when the JavaScript dependencies are not installed. Install them with:
opam install js_of_ocaml js_of_ocaml-ppxBuild the artifact with:
dune build bin/pgn_to_tex_js.bc.jsThe generated CommonJS file is _build/default/bin/pgn_to_tex_js.bc.js. It works in Node and in the browser.
The build exposes a single global function with the same three inputs as the JavaScript entry point:
to_tex(pgnString, diagramJsonString, displayClock);pgnStringis the PGN text.diagramJsonStringis the diagram JSON. Pass""or"{}"when there are no diagrams.displayClockis a boolean controlling clock rendering.- The return value is the LaTeX fragment as a string.
Node example:
require("./_build/default/bin/pgn_to_tex_js.bc.js");
const gameTex = globalThis.to_tex("1. e4 e5 2. Nf3", "{}", false);In the browser, the function is available as window.to_tex after the artifact is loaded.
Run the Node smoke test after building the artifact:
node test/js/smoke.jsMigration from 0.0.x
Version 0.1.0 is a breaking release. The old Pgn_logic library name and the Pgn2tex module are gone.
0.0.x | 0.1.0 |
|---|---|
|
|
|
|
|
|
Exposed | Abstract |
|
|
The optional arguments now come before the PGN string:
(* 0.0.x *)
let tex = Pgn2tex.to_tex pgn ~diagram_data ~clock:false
(* 0.1.0 *)
let tex = Pgn_to_tex.to_tex ~diagram_data ~clock:false pgnReplace a hand-built diagram map with parse_diagrams_json, or use empty_diagrams for a game without diagrams. Path-only diagram entries are supported; see Diagram JSON schema. The JavaScript to_tex(pgn, diagramJson, displayClock) contract is unchanged apart from the artifact name.
Dependencies
Runtime and build dependencies:
ocaml>= 5.4dune>= 3.21sedlex>= 3.7menhir>= 20260209yojson>= 3.0
Test-only dependencies:
ounit2>= 2.2.7js_of_ocamljs_of_ocaml-ppx
Development and documentation dependencies:
ocamlformat0.29.0 (development setup)odoc(documentation build)
License
AGPL-3.0-or-later. See LICENSE.
Dev Dependencies (5)
-
odoc
with-doc -
ocamlformat
with-dev-setup & = "0.29.0" -
js_of_ocaml-ppx
with-test -
js_of_ocaml
with-test -
ounit2
with-test & >= "2.2.7"
Used by
None
Conflicts
None