feat: add preview tuning — hi-DPI PDF previewer and opener setup

preview/ holds a yazi.toml and a pdf-hidpi plugin that fix unreadable PDF
previews. Yazi calls pdftoppm without -r (150 DPI) and then caps the image at
600x900, so pages are downscaled and upscaled again; the plugin renders at
300 DPI as PNG and the cap goes to 1600x2400, turning that into supersampling.

Previewer and preloader both have to be overridden — they share the cache file.

README documents this plus what video previews can and cannot do: the preview
is a single ffmpeg keyframe by design, J/K scrubs it, and the opener offers
mpv --vo=kitty with shared-memory transfer for inline playback.

Targets the 26.9 plugin API (fs.cha, not main's fs.stat).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
thomas.koppandClaude Opus 5 committed 2026-09-23 20:12:42 +02:00
1 parent 6dc8e1b3ae
commit de59a5a904
3 files changed
+176

No files matched your search

+65
View File
@@ -74,6 +74,66 @@ set keeps the default.
Both files are needed — `theme.toml` points at the `.tmTheme` for syntax
highlighting in file previews.
## Sharper previews (optional)
Not part of the colour scheme, but it belongs in the same corner of
`~/.config/yazi` — the files are under `preview/`.
```sh
cp preview/yazi.toml ~/.config/yazi/yazi.toml
cp -r preview/plugins/pdf-hidpi.yazi ~/.config/yazi/plugins/
ya cache clear
```
### Why PDFs look mushy by default
Two bottlenecks stack up:
1. Yazi calls `pdftoppm` **without `-r`**, so pages render at poppler's 150 DPI
default — an A4 page is 1241x1754 px.
2. `[preview] max_width` / `max_height` default to **600x900**. The page is
squeezed to 600 px wide and then scaled back up to fill the pane.
On top of that the page is JPEG-compressed at quality 75, which smears glyph
edges.
`preview/yazi.toml` raises the cap to 1600x2400, and the `pdf-hidpi` plugin is
Yazi's own PDF previewer with two changes: `-r 300` and PNG instead of JPEG.
The page now renders at 2481x3508 and is downscaled into the cap, so the result
is supersampled rather than upscaled — and lossless where the glyphs are.
**Both the previewer and the preloader must be overridden.** They write the
same cache file, so replacing only one leaves the 150 DPI version in place.
`preview/yazi.toml` does both.
Yazi has no zoom. What it has is `J` / `K`, which turn PDF pages in the
preview, and whatever key you bound to maximise the preview pane.
### Videos
The video preview is **one still frame** by design: `ffmpeg` pulls a keyframe
at 5 % of the runtime and Yazi shows it as an image. Nothing animates, and no
setting changes that.
What does work:
| Key | |
|---|---|
| `J` / `K` | scrub the preview through the file, ±5 % per press |
| `Tab` | spot panel — codec, resolution, duration |
| `Enter` / `o` | open in an mpv window |
| `O` | pick an opener — including **Play here (Kitty)** |
The last one is the `[opener]` block in `preview/yazi.toml`: it runs
`mpv --vo=kitty --vo-kitty-use-shm=yes`, which draws the video into the
terminal pane through the Kitty graphics protocol. Yazi blocks until mpv
exits (`q`). Shared-memory transfer keeps it watchable; without it every frame
goes through the tty as base64 and it crawls. It is still a terminal doing a
framebuffer's job — fine for checking what a clip is, not for watching it.
Defining `play` replaces Yazi's preset list rather than merging into it, which
is why mpv appears twice there: once windowed, once inline.
## Keeping the icon table current
The `[icon]` block **replaces** Yazi's preset table wholesale. Icons added by
@@ -91,6 +151,11 @@ script prints which Gruvbox tone each source colour landed on.
`regen-tmtheme.py` does the same for `gruvbox-dark.tmTheme` — edit the palette
or the scope rules at the top of the script and re-run it.
Both generators and the `pdf-hidpi` plugin target the **26.9** plugin API. If
you build against Yazi's `main` branch instead of the tag you run, you will hit
renamed functions — `fs.cha()` was `fs.stat()` on main — and the error only
surfaces at preview time.
## Self-contained
Everything needed is in this repository — nothing has to be fetched from a