lichess-study-to-pdf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. lichess_study_to_pdf-0.1.0/LICENSE +21 -0
  2. lichess_study_to_pdf-0.1.0/PKG-INFO +546 -0
  3. lichess_study_to_pdf-0.1.0/README.md +507 -0
  4. lichess_study_to_pdf-0.1.0/lichess_study_pdf/__init__.py +3 -0
  5. lichess_study_to_pdf-0.1.0/lichess_study_pdf/cli.py +354 -0
  6. lichess_study_to_pdf-0.1.0/lichess_study_pdf/evals.py +418 -0
  7. lichess_study_to_pdf-0.1.0/lichess_study_pdf/fetch.py +343 -0
  8. lichess_study_to_pdf-0.1.0/lichess_study_pdf/fonts.py +153 -0
  9. lichess_study_to_pdf-0.1.0/lichess_study_pdf/notation.py +147 -0
  10. lichess_study_to_pdf-0.1.0/lichess_study_pdf/parse.py +329 -0
  11. lichess_study_to_pdf-0.1.0/lichess_study_pdf/paths.py +82 -0
  12. lichess_study_to_pdf-0.1.0/lichess_study_pdf/pdf.py +1082 -0
  13. lichess_study_to_pdf-0.1.0/lichess_study_pdf/pdf_acrobat.py +316 -0
  14. lichess_study_to_pdf-0.1.0/lichess_study_pdf/pdf_latex.py +520 -0
  15. lichess_study_to_pdf-0.1.0/lichess_study_pdf/render.py +188 -0
  16. lichess_study_to_pdf-0.1.0/lichess_study_pdf/server.py +552 -0
  17. lichess_study_to_pdf-0.1.0/lichess_study_pdf/sidelines.py +132 -0
  18. lichess_study_to_pdf-0.1.0/lichess_study_pdf/studies.py +195 -0
  19. lichess_study_to_pdf-0.1.0/lichess_study_pdf/web/app.js +1032 -0
  20. lichess_study_to_pdf-0.1.0/lichess_study_pdf/web/index.html +184 -0
  21. lichess_study_to_pdf-0.1.0/lichess_study_pdf/web/style.css +355 -0
  22. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/PKG-INFO +546 -0
  23. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/SOURCES.txt +28 -0
  24. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/dependency_links.txt +1 -0
  25. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/entry_points.txt +2 -0
  26. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/requires.txt +12 -0
  27. lichess_study_to_pdf-0.1.0/lichess_study_to_pdf.egg-info/top_level.txt +1 -0
  28. lichess_study_to_pdf-0.1.0/pyproject.toml +56 -0
  29. lichess_study_to_pdf-0.1.0/setup.cfg +4 -0
  30. lichess_study_to_pdf-0.1.0/tests/test_study.py +609 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shubhro Dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,546 @@
1
+ Metadata-Version: 2.4
2
+ Name: lichess-study-to-pdf
3
+ Version: 0.1.0
4
+ Summary: Turn a Lichess study into a PDF you can step through move by move.
5
+ Author-email: Shubhro Dev <shubhro2004@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/spearb0lt/Lichess-Essentials
8
+ Project-URL: Repository, https://github.com/spearb0lt/Lichess-Essentials
9
+ Project-URL: Issues, https://github.com/spearb0lt/Lichess-Essentials/issues
10
+ Project-URL: Documentation, https://github.com/spearb0lt/Lichess-Essentials/blob/main/Lichess-Study-to-PDF/README.md
11
+ Keywords: chess,lichess,study,pdf,pgn,export,printable
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Games/Entertainment :: Board Games
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: chess>=1.11
28
+ Requires-Dist: reportlab>=4.0
29
+ Requires-Dist: svglib>=1.5
30
+ Requires-Dist: pikepdf>=8.0
31
+ Requires-Dist: requests>=2.31
32
+ Requires-Dist: fastapi>=0.110
33
+ Requires-Dist: uvicorn[standard]>=0.27
34
+ Requires-Dist: pydantic>=2.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=8.0; extra == "dev"
37
+ Requires-Dist: pypdfium2>=4.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # Lichess Study to PDF
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/lichess-study-to-pdf?logo=pypi&logoColor=white)](https://pypi.org/project/lichess-study-to-pdf/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/lichess-study-to-pdf)](https://pypi.org/project/lichess-study-to-pdf/)
44
+ [![Downloads](https://static.pepy.tech/badge/lichess-study-to-pdf)](https://pepy.tech/project/lichess-study-to-pdf)
45
+ [![Downloads](https://static.pepy.tech/badge/lichess-study-to-pdf/month)](https://pepy.tech/project/lichess-study-to-pdf)
46
+ [![License](https://img.shields.io/pypi/l/lichess-study-to-pdf)](LICENSE)
47
+
48
+
49
+ Turn a Lichess study into a PDF worth reading, plus a browser interface for
50
+ working through it first.
51
+
52
+ Everything in the study makes it into the export: main line, sidelines nested
53
+ to any depth, comments, NAG symbols (`!`, `?!`, `□`), and the coloured square
54
+ markers and arrows Lichess stores in the PGN.
55
+
56
+ ![The Fried Liver Attack study open in the browser: the chapter list on the left, the board with a live eval bar, the notation panel with comments and sideline colours, and the eval graph underneath](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/study.png)
57
+
58
+ ---
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install lichess-study-to-pdf
64
+ lichess-study-pdf serve
65
+ ```
66
+
67
+ Or take all five at once with `pip install lichess-essentials`. Installed this
68
+ way your files live in the usual per-user folder for your platform, and the
69
+ app prints the path in its startup banner. To run it from a checkout instead,
70
+ see [the repository README](https://github.com/spearb0lt/Lichess-Essentials/blob/main/README.md#setup-from-a-checkout).
71
+
72
+ ## Running the app
73
+
74
+ ### Step 1 — set up, once
75
+
76
+ The virtualenv lives at the **repository root**, one level above this folder,
77
+ and is shared by every app in the repo.
78
+
79
+ <details open>
80
+ <summary><b>Windows (PowerShell)</b></summary>
81
+
82
+ ```powershell
83
+ cd "C:\Users\<you>\Documents\GitHub\Lichess-Essentials"
84
+ python -m venv .lichess
85
+ .\.lichess\Scripts\python.exe -m pip install -r Lichess-Study-to-PDF\requirements.txt
86
+ ```
87
+ </details>
88
+
89
+ <details>
90
+ <summary><b>Windows (Git Bash) / macOS / Linux</b></summary>
91
+
92
+ ```bash
93
+ cd ~/Documents/GitHub/Lichess-Essentials
94
+ python -m venv .lichess
95
+
96
+ # Git Bash on Windows
97
+ ./.lichess/Scripts/python.exe -m pip install -r Lichess-Study-to-PDF/requirements.txt
98
+
99
+ # macOS / Linux
100
+ ./.lichess/bin/python -m pip install -r Lichess-Study-to-PDF/requirements.txt
101
+ ```
102
+ </details>
103
+
104
+ You only ever do this once.
105
+
106
+ ### Step 2 — start the web app
107
+
108
+ **Run it from inside the `Lichess-Study-to-PDF` folder** — that is where the
109
+ `lichess_study_pdf` package lives, and Python needs to see it.
110
+
111
+ ```powershell
112
+ # Windows PowerShell
113
+ cd "C:\Users\<you>\Documents\GitHub\Lichess-Essentials\Lichess-Study-to-PDF"
114
+ & "..\.lichess\Scripts\python.exe" -m lichess_study_pdf.cli serve
115
+ ```
116
+
117
+ ```bash
118
+ # Git Bash on Windows
119
+ cd ~/Documents/GitHub/Lichess-Essentials/Lichess-Study-to-PDF
120
+ ../.lichess/Scripts/python.exe -m lichess_study_pdf.cli serve
121
+
122
+ # macOS / Linux
123
+ cd ~/Documents/GitHub/Lichess-Essentials/Lichess-Study-to-PDF
124
+ ../.lichess/bin/python -m lichess_study_pdf.cli serve
125
+ ```
126
+
127
+ You should see:
128
+
129
+ ```
130
+ Lichess Study to PDF is running.
131
+ Open http://127.0.0.1:8777 in your browser.
132
+ engine : ...\engine\stockfish-windows-x86-64-bmi2.exe
133
+ LaTeX : ...\MiKTeX\miktex\bin\x64\pdflatex.EXE
134
+ Press Ctrl+C to stop.
135
+ ```
136
+
137
+ Those two lines tell you what will work: no engine means blank eval bars, no
138
+ LaTeX means the book mode is greyed out. Neither stops the app running.
139
+
140
+ ### Step 3 — use it
141
+
142
+ 1. Open <http://127.0.0.1:8777>. It opens on **My studies** — your own list
143
+ of studies as clickable cards, named, so you can see what you are opening.
144
+ See [Your studies list](#your-studies-list) below.
145
+ 2. Click one, or paste a study URL and press **Load study**.
146
+ For a **private** study, paste a *chapter* URL
147
+ (`https://lichess.org/study/i7hMEq7h/0KOpBPyc`) — every other chapter is
148
+ found automatically, no token needed. See the next section.
149
+ 3. Click chapters on the left; step through with **Space**, the arrow keys, or
150
+ by **scrolling the mouse wheel over the board** — down goes forward, up goes
151
+ back, and either one stops the autoplay.
152
+ Click any move — including inside a sideline — to jump there.
153
+ Pick up a piece to play your own moves from that position.
154
+ 4. **Export PDF** on the bottom left, choose a style, **Build PDF**.
155
+
156
+ ![The Export to PDF dialog: a style picker, the diagram policy, a chapter subset, and tick boxes for the notation section, stepping pages, evaluation bars and landscape pages](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/export.png)
157
+
158
+ Handy: `http://127.0.0.1:8777/?url=<study-url>` loads a study straight away,
159
+ so you can bookmark a study you open often.
160
+
161
+ ### Your studies list
162
+
163
+ ![The home page: My studies, one card per study, grouped under the section headings from studies.txt](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/home.png)
164
+
165
+ The home page is built from **`studies.txt`**, in this folder. One study per
166
+ line:
167
+
168
+ ```text
169
+ ## Openings
170
+ Fried Liver Attack Full Guide | https://lichess.org/study/i7hMEq7h/T5rBUcOn
171
+ Anti-Sicilian Repertoire | https://lichess.org/study/UYLsUjvy
172
+ https://lichess.org/study/EY8AUyPd
173
+ ```
174
+
175
+ - `Name | URL` — the name is what the card says, the URL is what it opens.
176
+ - The name is optional: a bare URL works, the card just shows the study id.
177
+ - `## Something` starts a section, `#` starts a comment, blank lines are
178
+ ignored.
179
+ - A line that is neither a comment nor a study is reported under the list
180
+ rather than throwing the rest of it away, so one typo costs you nothing.
181
+ - Put a **chapter** URL in for a private study, as the Fried Liver line does:
182
+ that is what lets it open without a token.
183
+
184
+ Two ways to add to it:
185
+
186
+ - **Edit the file.** The page re-reads it on every refresh — no restart.
187
+ - **Press ☆ Save** in the header while a study is open. It appends the study
188
+ under a `## Saved from the app` section with its real name filled in.
189
+ Saving one twice does nothing.
190
+
191
+ `LICHESS_STUDIES_FILE=/some/other/path.txt` points the app at a different
192
+ list, if you would rather keep yours outside the repository.
193
+
194
+ There is no Lichess API for the studies you have *liked*, so the list cannot
195
+ be filled from your Lichess favourites automatically. What Lichess does expose
196
+ is every study belonging to an account (`/api/study/by/<username>`), so a list
197
+ of your own studies can be generated if you want one.
198
+
199
+ ### Step 4 — stop it
200
+
201
+ `Ctrl+C` in the terminal you started it in.
202
+
203
+ ### Other ports
204
+
205
+ ```bash
206
+ ... cli serve --port 8899 # if 8777 is taken
207
+ ... cli serve --host 0.0.0.0 # reachable from other devices on your LAN
208
+ ```
209
+
210
+ `--host 0.0.0.0` exposes the app to your whole network and there is no
211
+ authentication — only do it on a network you trust.
212
+
213
+ ---
214
+
215
+ ## Without the browser: straight to a PDF
216
+
217
+ Same folder, same interpreter, no server involved:
218
+
219
+ ```bash
220
+ cd Lichess-Study-to-PDF
221
+
222
+ # the default: twelve small boards to a page
223
+ ../.lichess/Scripts/python.exe -m lichess_study_pdf.cli \
224
+ "https://lichess.org/study/i7hMEq7h/0KOpBPyc" -o repertoire.pdf
225
+
226
+ # a typeset chess book
227
+ ../.lichess/Scripts/python.exe -m lichess_study_pdf.cli \
228
+ "https://lichess.org/study/i7hMEq7h/0KOpBPyc" --mode book -o book.pdf
229
+
230
+ # one big board per page, steps with the arrow keys
231
+ ../.lichess/Scripts/python.exe -m lichess_study_pdf.cli \
232
+ "https://lichess.org/study/ByhlXnmM" --mode slideshow -o study.pdf
233
+
234
+ # what engine did it find?
235
+ ../.lichess/Scripts/python.exe -m lichess_study_pdf.cli engine-info
236
+ ```
237
+
238
+ Full option list: `... cli export --help`, or the CLI reference further down.
239
+
240
+ ---
241
+
242
+ ## If something goes wrong
243
+
244
+ | Symptom | Cause and fix |
245
+ |---|---|
246
+ | `No module named lichess_study_pdf` | You are in the wrong directory. `cd` into `Lichess-Study-to-PDF` first. |
247
+ | `Port 8777 is already in use` | The app is probably already running — open the browser, or `--port 8899`. |
248
+ | `No module named 'chess'` (or `fastapi`, `reportlab`, …) | You ran the system Python instead of the venv one. Use the full `..\.lichess\Scripts\python.exe` path. |
249
+ | Eval bars are blank | No Stockfish. Run `engine-info` and drop a binary in `engine/`. |
250
+ | Book mode greyed out | No `pdflatex`. Install MiKTeX or TeX Live, or use Slideshow. |
251
+ | `403 ... study is private` | Paste a **chapter** URL instead of the study URL, or supply a token. |
252
+ | PDF looks blank except the first position of each chapter | You exported in **Acrobat** mode. Re-export as Book or Slideshow. |
253
+
254
+ ---
255
+
256
+ ## Private studies work without a token
257
+
258
+ Lichess is inconsistent about study privacy, and this tool exploits that:
259
+
260
+ ```
261
+ GET /api/study/<study>.pgn -> 403 for a private study
262
+ GET /api/study/<study>/<chapter>.pgn -> 200, full PGN, no token
263
+ ```
264
+
265
+ The per-chapter endpoint does not enforce the study's privacy. So **paste a
266
+ chapter URL** and the whole study comes down:
267
+
268
+ ```
269
+ https://lichess.org/study/i7hMEq7h <- 403, private
270
+ https://lichess.org/study/i7hMEq7h/0KOpBPyc <- works, and finds the other 12
271
+ chapters automatically
272
+ ```
273
+
274
+ The chapter's own page lists every chapter in the study, so one chapter URL is
275
+ enough to rebuild all of it. Open your study on Lichess, click any chapter,
276
+ copy that address.
277
+
278
+ A token is still supported and is the documented route — create one with the
279
+ `study:read` scope at
280
+ <https://lichess.org/account/oauth/token/create?scopes[]=study:read>, then
281
+ `--token`, `LICHESS_TOKEN`, or `~/.lichess_token`.
282
+
283
+ ---
284
+
285
+ ## The four export styles
286
+
287
+ Every chapter starts on a fresh page in all of them, and every sideline is
288
+ given its own colour, so two alternatives to the same move never look alike.
289
+
290
+ Each colour arrives in three matching tones: a **bar** down the left edge of
291
+ the board or notation block, a mild **wash** behind it, and the **ink** of its
292
+ moves. Numbering is chapter-wide — a branch point hands its alternatives a
293
+ consecutive run of colours (which the palette spaces ~105° apart on the wheel),
294
+ and a sideline nested inside another gets one of its own, so nothing that a
295
+ reader sees at once shares a colour. The palette holds 24; after that colours
296
+ repeat, which only ever affects sidelines pages apart.
297
+
298
+ Every sideline also carries its number — `s1`, `s2`, … printed where it opens,
299
+ in the grid cell, in the breadcrumb — so the colour has a name. That, the bar,
300
+ the indent and the depth dots are all shape rather than hue, which is what
301
+ keeps nesting and identity readable in a greyscale print or for a colour-blind
302
+ reader. Grid pages carry a legend of the sidelines shown on them along the
303
+ footer.
304
+
305
+ ![A grid page carrying two sidelines, s2 in green and s4 in magenta: each is a bar down the left edge of its boards, a wash behind them and the ink of their moves, its number printed in every cell, and both named in the legend along the footer](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/pdf-grid-sidelines.png)
306
+
307
+ ### `--mode grid` (default) — twelve boards to a page
308
+
309
+ A contact sheet: every position gets its own diagram, twelve to a page, in
310
+ reading order, each with its move, evaluation and comment underneath. Same
311
+ coverage as the slideshow with a twelfth of the diagram pages — measured on a
312
+ 237-position study, 20 pages instead of 237.
313
+
314
+ ![A grid page: twelve boards in reading order, each with its own eval bar, and its move, evaluation and comment underneath -- with the study's own arrows and circles drawn on the diagrams](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/pdf-grid.png)
315
+
316
+ Comments are trimmed to two lines in a grid cell; the notation section, which
317
+ is on by default, still carries every comment in full.
318
+
319
+ Two things that make a page hold fewer than twelve:
320
+
321
+ * **Short chapters.** Chapters always start on a fresh page, so a chapter with
322
+ six positions gets a page with six boards. That is the direct cost of the
323
+ one-chapter-per-page rule.
324
+ * **The notation section.** It is a separate, text-only section — it does not
325
+ put boards on its pages in grid mode, because the grid already shows every
326
+ position. `--no-notation` drops it entirely if you only want diagrams.
327
+
328
+ `--diagrams` controls diagrams *inside the notation section* only, never the
329
+ grid. It defaults to automatic: `none` in grid mode, `every:6` elsewhere.
330
+
331
+ ```
332
+ --grid-columns 4 --grid-rows 3 # the default 12 per page
333
+ --grid-columns 3 --grid-rows 2 # 6 bigger boards per page
334
+ ```
335
+
336
+ ### `--mode book` — a typeset chess book
337
+
338
+ Compiled with LaTeX (`xskak` + `chessboard`): portrait, two columns,
339
+ justified Computer Modern, figurine notation (`♘f3`), printed-book diagrams
340
+ with hatched squares and a side-to-move marker, arrows and circles from the
341
+ study's own annotations, and optional `[+0.42]` evaluations beside each move.
342
+
343
+ A 13-chapter study lands in about 16 pages. This is the mode to use for
344
+ reading and printing.
345
+
346
+ ![A book page: two columns of justified figurine notation with bracketed evaluations, and printed-book diagrams carrying the study's arrows and a side-to-move marker](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Lichess-Study-to-PDF/docs/pdf-book.png)
347
+
348
+ Needs `pdflatex` (MiKTeX or TeX Live) with `xskak`, `chessboard`, `skak`.
349
+ Without it the mode is disabled in the UI and the CLI says so.
350
+
351
+ ### `--mode slideshow` — one big board per page
352
+
353
+ One position per page, so your reader's ordinary next-page key — space,
354
+ arrow, PageDown, a presentation remote, a tap on a phone — steps the board
355
+ forward one move. No scripting, so it behaves identically in every viewer.
356
+
357
+ Each page carries the board, the eval bar, the current line with the move
358
+ boxed, upcoming moves greyed ahead of it, and the comment. It is long by
359
+ nature — use `grid` unless you specifically want to step move by move.
360
+
361
+ ### `--mode acrobat` — layered, Adobe Reader only
362
+
363
+ Each chapter is a single page holding every position as a PDF optional-content
364
+ layer, switched by embedded JavaScript.
365
+
366
+ **Only Adobe Acrobat Reader executes PDF JavaScript.** Everywhere else you see
367
+ the first position of each chapter and the buttons do nothing. The file now
368
+ carries a full-page warning saying exactly that, because this mode is easy to
369
+ pick by accident and the result looks broken rather than limited.
370
+
371
+ ---
372
+
373
+ ## Evaluation bars
374
+
375
+ Two sources, in order:
376
+
377
+ 1. **Lichess cloud eval** — instant, but only for positions already in its
378
+ cache (in practice, openings), and **firmly rate limited**: a few hundred
379
+ lookups earns a `429` that lasts minutes. So the cloud is only asked about
380
+ positions up to move 20, paced about a second apart, and a `429` is
381
+ recorded and skipped rather than waited on. It never blocks the UI.
382
+ 2. **Local Stockfish** — full coverage, and what actually evaluates a personal
383
+ repertoire.
384
+
385
+ Put a Stockfish binary in `engine/`, on `PATH`, or at `$STOCKFISH_PATH`;
386
+ `engine-info` tells you what it found. Results are cached in
387
+ `~/.cache/lichess-study-pdf/evals.json`, keyed so cloud results are reused
388
+ regardless of engine settings.
389
+
390
+ **Export evaluations are computed on the server** for every position being
391
+ exported. Earlier versions shipped whatever the browser happened to have,
392
+ which meant most positions came out blank — that is fixed. Expect roughly
393
+ 30 s for a 240-position study.
394
+
395
+ ---
396
+
397
+ ## The web interface
398
+
399
+ Modelled on [chesspaper.me](https://chesspaper.me/), with the gaps filled in:
400
+
401
+ | | chesspaper.me | this |
402
+ |---|---|---|
403
+ | Sidelines and comments | need per-node toggling | all visible from the start |
404
+ | Board stepping | no next control | **Space / arrow keys / Next button** |
405
+ | Which line you step | — | follows whichever line you clicked into |
406
+ | Eval bar | — | live for the position you are on, ~150 ms |
407
+ | Play your own moves | — | pick any piece up, from any position |
408
+ | Diagrams in the PDF | manual toggle per node | one setting for the whole study |
409
+
410
+ **Free play.** Click a piece and its legal moves light up; click a destination
411
+ and you are off the study line, with a banner telling you how many moves deep
412
+ you are and a button back. Evaluations keep coming for every move you invent.
413
+ Left arrow takes back, Escape returns to the line. Legality is checked
414
+ server-side by python-chess, so there is no chess library in the browser.
415
+
416
+ **Hover preview.** Hovering any move in the notation pops up a small board of
417
+ that position, so you can scan a sideline without leaving where you are.
418
+
419
+ Keyboard: `Space`/`→`/`↓` next, `←`/`↑` back, `Home`/`End` first/last,
420
+ `F` flip, `P` autoplay, `Esc` cancel selection or leave free play.
421
+
422
+ ---
423
+
424
+ ## CLI reference
425
+
426
+ ```
427
+ lichess-study-pdf <study-url|chapter-url> [options]
428
+
429
+ -o, --output PATH output file
430
+ --token TOKEN Lichess API token (study:read)
431
+ --pgn FILE read a local PGN instead of calling the API
432
+ --chapter-only with a chapter URL, export only that chapter
433
+ --save-pgn FILE also save the downloaded PGN
434
+
435
+ --mode MODE grid (default) | book | slideshow | acrobat
436
+ --grid-columns N boards across the page in grid mode (default 4)
437
+ --grid-rows N boards down the page in grid mode (default 3)
438
+ --latex PATH pdflatex binary for --mode book
439
+ --keep-tex PATH also write the generated .tex
440
+ --no-notation skip the read-through notation section
441
+ --no-steps skip the one-page-per-position section
442
+ --chapters SPEC subset, 1-based, e.g. 1,3,5-8
443
+ --max-depth N drop sidelines nested deeper than N
444
+ --diagrams POLICY none | comments | all | every:N (default every:6)
445
+ --page-size SIZE a4 | a3 | letter
446
+ --portrait portrait pages (book mode is always portrait)
447
+
448
+ --no-evals no evaluation bars
449
+ --no-cloud engine only, skip the Lichess cloud
450
+ --engine PATH Stockfish binary
451
+ --movetime SEC seconds per position (default 0.25)
452
+ --depth N fixed depth instead of a time budget
453
+
454
+ lichess-study-pdf serve [--host H] [--port P]
455
+ lichess-study-pdf engine-info
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Hosting it for free
461
+
462
+ This is a plain FastAPI/Uvicorn app with one runtime dependency worth caring
463
+ about: Stockfish, invoked as a subprocess and kept warm for the life of the
464
+ process ([server.py](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Lichess-Study-to-PDF/lichess_study_pdf/server.py)). That rules out anything
465
+ serverless (Vercel, AWS Lambda-style hosts) — you need something that runs a
466
+ real, long-lived container. Two that do it for free:
467
+
468
+ | | Hugging Face Spaces | Render.com |
469
+ |---|---|---|
470
+ | Cost | Free, no card required | Free tier — check current signup terms, this has changed before |
471
+ | Runtime | Docker | Docker |
472
+ | Idle behaviour | Sleeps, wakes on the next visit | Spins down after ~15 min idle; cold start on the next request |
473
+
474
+ [`Dockerfile`](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Lichess-Study-to-PDF/Dockerfile) in this folder installs Stockfish via `apt-get`
475
+ (Debian's package, not a manual binary download) and deliberately skips
476
+ LaTeX — a full texlive install is several GB and not worth it unless you
477
+ specifically want book mode. It sets `STOCKFISH_PATH` to the apt package's
478
+ install location so `find_stockfish()` finds it without relying on `PATH`.
479
+
480
+ ### Render
481
+
482
+ 1. Push this repo to GitHub.
483
+ 2. **New Web Service** → connect the repo → **Root Directory**:
484
+ `Lichess-Study-to-PDF` → Render auto-detects the Dockerfile → **Free**
485
+ instance type → deploy.
486
+ 3. You get a URL like `https://<name>.onrender.com`.
487
+
488
+ ### Hugging Face Spaces
489
+
490
+ Spaces are their own separate git repo (not this GitHub repo), so:
491
+
492
+ 1. **New Space** → **SDK: Docker** → **Hardware: CPU basic (free)**.
493
+ 2. Clone the Space's repo locally, then copy this folder's **contents**
494
+ (`Dockerfile`, `requirements.txt`, `lichess_study_pdf/`, etc.) into its
495
+ root — not the `Lichess-Study-to-PDF` folder itself, what's inside it.
496
+ 3. Add this to the top of the Space's `README.md`:
497
+ ```yaml
498
+ ---
499
+ title: Lichess Study to PDF
500
+ sdk: docker
501
+ app_port: 7860
502
+ ---
503
+ ```
504
+ 4. Commit and push (a Hugging Face access token as the git password).
505
+
506
+ ### Before you make it public
507
+
508
+ - **Don't set `LICHESS_TOKEN` on a public deployment.** It would be shared by
509
+ every visitor — anyone with the URL could pull whatever private studies
510
+ that token can see. Leave it unset; paste a token into the UI per session
511
+ instead, the same as running it locally.
512
+ - There is no password wall here by default — anyone with the link can use
513
+ the tool (not access your Lichess account, just use the app). If you want
514
+ one, the sibling app has it wired up — see
515
+ [Repertoire Creator's hosting section](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Repertoire-Creator/README.md#hosting-it-for-free)
516
+ for the pattern; it is not implemented in this app.
517
+
518
+ ---
519
+
520
+ ## Layout
521
+
522
+ ```
523
+ lichess_study_pdf/
524
+ fetch.py URL -> PGN, token handling, private-study chapter fallback
525
+ parse.py PGN -> chapters -> depth-first list of positions
526
+ notation.py notation blocks and move-tree helpers
527
+ render.py board SVG -> vector drawing, eval bar
528
+ fonts.py Unicode font resolution (□ ± ∞ would break Helvetica)
529
+ pdf.py grid + slideshow writers, title/contents/notation
530
+ pdf_latex.py the LaTeX chess book
531
+ pdf_acrobat.py optional-content layers + embedded JavaScript
532
+ evals.py cloud eval, Stockfish, non-blocking back-off, disk cache
533
+ cli.py command line
534
+ server.py FastAPI backend, warm engine singleton
535
+ web/ browser interface (no build step, plain JS)
536
+ ```
537
+
538
+ `../.lichess/Scripts/python -m pytest tests -q` runs the suite. The test that
539
+ matters most replays every position's recorded line and asserts it reaches
540
+ that position's FEN — that is what guarantees no sideline is misattached.
541
+
542
+ ## Licence
543
+
544
+ MIT. Chess piece artwork in the SVG boards comes from python-chess (Colin
545
+ M.L. Burnett's Cburnett set, CC BY-SA 3.0); the book mode's diagrams come from
546
+ the LaTeX `chessboard` package.