[DRAFT] Understanding Org's LaTeX preview system

TL;DR: LaTeX in Org-mode now go whoosh, all pretty like

After waffling about the inadequacy of LaTeX previews in Emacs – and specifically Org mode – for over a decade, Tecosaur and I decided to take the leap and rewrite it from the ground up. The result is a live-previewing experience for LaTeX math that can provide near-WYSIWYG feedback but retains the properties we expect in the Emacs environment: purely text-based, extensible and composable.

The result is not much code, but it contains many moving pieces and makes contact with several corners of Emacs’ API.

I thought I would take this opportunity to document how it works, from the ground up, before my grasp of the totality of the code frays with time.

What this document is not:

  • It is not an exhaustive demo of all the shiny new features, although there are a few videos peppered through the first half of this web page. Moreover, there’s already a demo that covers most of the improvements.
  • It is not a user manual for org-latex-preview. If you want to know how to enable live-previews, request more features, or report a bug with the rendering process, please use one of the official channels (Org mailing list, IRC or Matrix rooms)
  • It is not the official documentation of the code. In fact, it goes out of its way to avoid mentioning commands or user options by name, since these are subject to change.

Instead, it aims to provide an overview of the goals and design of the library, along with some explainers of relevant aspects of the Emacs API and tips to write your own features. If you are interested in

  • A broad understanding of LaTeX preview generation,
  • Learning about process interaction in Emacs
  • Building a non-trivial Emacs “application” from scratch

this write-up is for you.

What is org-latex-preview?

This is a feature included with Org mode (past and future versions) to preview LaTeX math snippets as images in Emacs buffers. The contents of LaTeX environments in Org mode are replaced with rendered images.

It has existed for decades at this point, but this rewrite overhauls the code base and adds a number of quality of life features “Quality of life features” is putting it mildly. I consider the old system unusable. . There is a 13 minute demo that can give you a good idea of the new capabilities of Org mode:

The bullet points, if you prefer:

  • Preview generation is fully asynchronous1
  • Bulk generation: Preview 200 to 800 snippets per second.
  • Images are placed continuously as they are generated
  • Previews blend in correctly with surrounding colors
  • Automatically match the current Emacs theme (even through theme changes)
  • Previews are aligned with and scaled to match the font baseline and size
  • Previews auto-generate for newly inserted LaTeX
  • Previews auto-update as you change the underlying LaTeX
  • Preview as you type: Live-previews of LaTeX fragments
  • Consistent (correct) equation numbering across the document
  • LaTeX compilation error reporting for previews

And a couple more quick demos:

If you are interested in what else is available or how to use it, you can check out Tecosaur’s This Month in Org news article. The rest of this document is focused (mostly) on how it works and (briefly) the why.


This document has a “fractal” level of detail. There is information

  • intended to give the casual reader a bird’s eye view of how LaTeX preview generation works,
  • intended to give the motivated reader a detailed look at the many pieces of the whole,
  • for the potential new Emacs package author looking for pointers on aspects of Emacs’ API,
  • for folks wanting to develop a similar feature for applications other than Emacs Hello there, Neovim users! ,
  • and information that is of no use whatsoever except to the future maintainers of the org-latex-preview library.

To that end, many details are tucked away into blocks like the following.

Implementation details

Blocks like this describe the concrete implementation of aspects of the code.

This is probably only interesting if you are looking to dive into the code itself, or if you are looking to write a similar package.

Emacs API notes

Blocks like this are for additional notes on aspects of Emacs’ API, including the odd code snippets.

Warning: Here be idiosyncratic takes.

Org Mode notes

Blocks like this describe other Org features relevant to LaTeX preview generation.

Command output

Blocks like this describe CLI commands associated with this process, or their output.

You can safely ignore them in favor of the top level headings and the images and video demos.


Isn’t this a narrow feature to spend so much time on?

Indeed, this feature

  • requires a LaTeX distribution installed,
  • works only with Org mode,
  • is glued together from several processes (see below) and thus inefficient in the larger scale of things,
  • and is finicky, as all hyper-configurable software is.

Plus, LaTeX is not built with the aim of minimizing latency. Having to start multiple new process even for a one-character change places a hard cap on what’s possible in terms of real-time feedback.2

Moreover, as popular as Org mode is, it’s a small player in the space of markup languages. The fraction of Org and LaTeX users who would prefer to use Org mode to write LaTeX is smaller still. And while Org attempts to integrate well with LaTeX, navigating the mishmash of Org and LaTeX syntax presents a third learning curve on top of learning to use the two markup languages individually.

But the situation is not as hopeless as it seems.

First, Org mode is a very convenient format for writing compared to LaTeX. There is significantly less visual noise (think Markdown vs HTML), and Org provides an escape hatch for adding LaTeX verbatim. Org’s simple interface for shuffling text around, monitoring task progress and executing code blocks makes it easy for eventual LaTeX documents to begin as Org files.

Second, the fact that Org is versatile as a publishing tool implies that the benefits of better LaTeX support transfer over to other formats too, like HTML or ODT.

There is a wide swathe of applications built on Org-mode that benefit from support for LaTeX math input, such as Org Roam and Org Noter. For these applications converting Org documents to LaTeX is not the goal, as they are not primarily publication tools: they stick around as private digital archives, and better LaTeX support is a bonus to the user’s expressivity.

Real-time feedback when editing LaTeX snippets is a mighty convenience – a positive difference in kind to the user’s experience. I did not realize this when I started work on org-latex-preview, but now I wouldn’t want to LaTeX without it It baffles me that web LaTeX editors (Overleaf etc) don’t provide this out of the box, especially as they target novice LaTeX users. .

Finally, everything in Emacs composes with everything else – including, for instance, your system for fast LaTeX input. We can keep Org-mode out of the picture: org-latex-preview can technically work anywhere in Emacs, it’s just not there yet See below if you are interested in developing this feature. .

But perhaps the most germane reason is that Emacs users don’t shy away from settling comfortably into tiny, cramped and poorly insulated niches. Comes with the territory, unfortunately!

The objectives

Async, Async, Async

Emacs users are closely acquainted with its frequent unresponsiveness. You’re never more than one minor-mode or mode line indicator away from causing Emacs to block or lag. While it’s not a big ask to wait a second for the result of an action you initiate, the unresponsiveness often manifests as short random freezes, which is unacceptable. But there’s more. Beyond the annoyance and inconvenience, poor responsiveness denies us an entire class of uses: real-time feedback is everything for interactive applications.

Owing to both its design and age, Emacs is not very well suited for asynchronous applications3. Certain patterns of asynchronous usage are viable (see below), but concurrency support is limited and parallelism is absent: no worker threads, channels, async-io, or anything of the like.

Generator-based async in Emacs lisp

I lied. In addition to Elisp threads, there exist prototype libraries for async-io (Chris Wellons) and channels/CSP (Andrey Listopadov) based on Emacs’ generators library, which in turn is implemented using continuation passing. But generators as provided by this library do not have anywhere near the level of robustness or support you need to build actual applications: no introspection, error handling or debugging support, poor performance, and just overall jankiness. I’ve tried and lived to regret it – Elfeed Tube is a real mess.

In any event, a more responsive org-latex-preview would require parallelism and not just concurrency. There is John Wiegley’s async library (and packages inspired by it, like session-async) that spawn a child Emacs that runs in parallel, but communication is limited and the overhead is sizeable, so this is a bit of a bludgeon.

The upshot is this: we want org-latex-preview to be as asynchronous as possible within Emacs’ single-threaded event loop model. The goal is to minimize how long the user has to wait, in terms of both the overall run time and the fraction of that for which Emacs is unresponsive. In fact we end up having to trade these off against each other.

Speed

A close second concern is preview generation speed. This is desirable both in the large and the small. Previewing upwards of a hundred LaTeX math fragments in a math-heavy Org document used to be exercise in patience. Now it takes about one second, and can be much faster with some trade-offs.

LaTeX Fragments Old preview system (s) org-latex-preview (s) Speed up
35 12.96 0.43 30x
70 22.59 0.51 44x
160 33.99 0.68 50x
330 84.95 1.32 66x
660 139.73 1.96 72x
1000 194.16 3.24 60x

Figure 1: LaTeX preview generation times under the slowest settings. The vertical axis is time, and along each vertical line the red chunks correspond to intervals for which Emacs is blocked. We still manage to process a file with 250 LaTeX fragments in about a second with minimal impact on Emacs’ responsiveness.

Figure 1: LaTeX preview generation times under the slowest settings. The vertical axis is time, and along each vertical line the red chunks correspond to intervals for which Emacs is blocked. We still manage to process a file with 250 LaTeX fragments in about a second with minimal impact on Emacs’ responsiveness.

Real-time feedback

Speeding up bulk generation is at most a boost in convenience. You don’t regenerate a large buffer’s worth of previews more than once every few sessions, and the resulting images are cached anyway. The more interesting aspect is that speed enables a close to real-time feedback loop that wasn’t available before. This is especially useful when you’re deep inside a messy structure like an array:

No more counting field separators. No more exiting, regenerating and re-examining snippets. It’s the best parts of WYSIWYG and visible markup, together.

Appearance

Even in the niche of academic-types who use Org mode with LaTeX, there are very different orientations. There’s Org the transient format, where an Org document starts out as a rough sketch, a thought bubble translated to the screen, and soon transitions into a TeX file that becomes the source of truth. There’s Org the intermediate publication format, where the document lives as an Org file, but is akin to uncooked dough – the value is in the baking, the web page or PDF document that it eventually becomes. For the adherents of this style the appearance of the Org document matters little.

Finally, there’s the rather new class of uses described above, where the desired output is the Org document itself, or a networked collection of them. These are folks who care about the aesthetics of Org documents and often suffer a thousand cuts from trying to make Emacs look “modern”.

One of the objectives of org-latex-preview is to ease their voluntary burden somewhat: LaTeX previews should blend in perfectly with the surrounding text. This means

  • preview image sizing that is consistent with surrounding text,
  • with perfect baseline alignment at any font size,
  • and Emacs theme-aware colors that change automatically with the theme.

Getting the fonts to match is possible too, but requires some degree of compromise on the part of the user You could start by using the variable-pitch Latin Modern font in your Org documents. .

Each of these is a long-standing issue with LaTeX previews in Org, and Stack Overflow and the usual haunts have decades of discussions on how to fix them, with band-aid after band-aid proposed as solutions. We took an elephant gun to this problem, starting by throwing out the giant mess that was Org’s accreted LaTeX preview code of the past two decades.

Here’s a demo of theme-aware LaTeX preview colors:

Windfalls: Consistent numbering, error reporting

Finally, there are a few features we picked up along the way because they were good hitchhikers.

Recording compilation errors was a simple matter since we scan the LaTeX output anyway.

Consistent equation numbering was expensive in the past because numbering changes can propagate across the document, and parsing the Org document and LaTeX fragment compilation were both slow. Recent changes to Org have made the former a non-issue (see the parsing section below), and the performance scaling on our bulk LaTeX runs have fixed the latter.

There are several more new minor features along for the ride, such as improvements to the org-edit-special (C-c ') experience when editing LaTeX.

Explainers: The Emacs API

As mentioned in the introduction, the LaTeX preview system makes contact with a few different facets of the Emacs API: mainly async process interaction (the “backend”) and overlays (the UI or “frontend”). Before we can dive into the design of org-latex-preview, here is a quick refresher on the limitations and capabilities of these two.

To join these pieces we use very simple, Lispy data structures. The live-updating preview system makes heavy use of hooks and timers. We also (naturally) use a few Org mode specific APIs, including ones developed by us for this purpose. We will cover all of these as we encounter them.

How Async works in Emacs

If you are reading this with a background in any modern programming language, we need to first understand how Emacs handles asynchronous interaction. (Or how it doesn’t.) A brief overview:

  1. Emacs can do only one thing at a time. As of Emacs 29. There is, in effect, a global interpreter lock. That means only one of: painting the screen (redisplay), updating syntax highlighting (fontification), running hook functions or timers, responding to window changes, handling keyboard input, and crucially for us – interacting with subprocesses. Every action is thus blocking.

  2. The only asynchronous interaction possible is by starting an OS-level subprocess. Emacs continues to run independently from the process, but it still has to manage it, i.e. handle the STDOUT and STDERR streams and respond to process signals. When it is doing this, it cannot pay attention to your input or anything else. Again, Emacs can only do one thing at a time.

    Starting asynchronous subprocesses

    There are multiple commands to start an asynchronous subprocess from Emacs (see M-x shortdoc process), but start-process covers most common use cases.

          (setq my-process-object
                (start-process "name" "buffer-name" "program" "--arg1" "--arg2" "..."))
    

    Processes are (optionally) associated with a buffer where process output is inserted. To send a subprocess some input, there’s process-send-string and process-send-region. Responding to process output and signals is the responsibility of process filters and sentinels (next block). kill-process does what you’d expect. All of these functions take the process object that start-process returns as an argument.

    Except for some fiddly details, that’s essentially all there is to the process interaction API. There are several variants of start-process available – the synchronous call-process, the shell versions start-process-shell-command and call-process-shell-command, and so on. The only one that does something different is the primitive make-process, which is a more extensive version that can (additionally) create network processes, send data through TCP or Unix sockets and so on.

  3. Emacs will try to prioritize user input events. To do this it can delay tasks like reading from subprcesses if necessary. Idle timers are another example of this. Some actions are thus delayable, but they block Emacs when they (eventually) run. There might be a penalty to pay afterwards, for instance when there’s more subprocess output to process as a result of delaying the task.

    Process filters and sentinels: Handling process output

    By default, Emacs inserts the output from a subprocess into the process buffer, or ignores it if there is no process buffer. (Crucially, it still has to read the output from the process pipe.) This is good enough for simple tasks. To use or transform subprocess output in more specialized ways, you can write an elisp function that is used as a process filter. It is called whenever Emacs reads new process output. In the language of shell commands, I think of process filters like this:

          $ start-process "some-process" 2>&1 | process-filter
    

    Process filters can be tricky to write if you want to parse structured process output, because the amount of data that Emacs reads from the pipe can be arbitrary. Unless latency is a concern, it’s often preferable to work with process sentinels for this reason. Sentinels are the second tool Emacs makes available for reacting to processes. A process sentinel is an elisp function (callback) called whenever the process changes state or raises a signal, including when it terminates. Practically speaking, I think of them like this:

          $ start-process "some-process" 2>&1 > process-buffer && \
              process-sentinel < process-buffer
    

    They are typically used for cleaning up after the process, and updating Emacs’ state depending on how the subprocess ends. But you can use them to access the process buffer containing all the process output, and thus do whatever you would in the filter.

    Filters and sentinels can be attached to a subprocess object created with start-process using set-process-filter and set-process-sentinel respectively.

  4. Many blocking operations can be interrupted by signalling a quit: this is what pressing C-g does, with some variations, in most contexts. Crucially for us, however, quitting is suppressed when Emacs is handling subprocess output or signals.4 If Emacs is blocked because it’s reading a large data dump… well, it’s blocked.

    Interrupting code

    keyboard-quit (C-g) is a specific input that interrupts any code, with a few exceptions.

    The obverse of this is to wrap code in a while-no-input clause – now any input will interrupt the processing of this specific block. This can be used to do background processing in small chunks without making Emacs unresponsive, but there are subtleties involved that I’m not familiar with. My understanding of while-no-input is theoretical.

The lack of concurrency has a flip side. It is significantly easier to write a large Emacs library than it is to write an equally versatile plugin for most multi-threaded GUI applications. I say this as a non-programmer who’s only managed to succeed in the former. We lean on the interpreter lock in many ways, choosing simple data structures and control flow that wouldn’t work if Emacs was multi-threaded. But most of Emacs wouldn’t work if Emacs was multi-threaded. In for a penny…

Displaying images over text

Overlays are a way to alter the appearance or behavior of regions of buffer text without modifying the text itself. Displaying images is one of their most common uses: LaTeX preview images are “overlaid” on top of the source fragments.

Overlays are used for one half of all display trickery in Emacs: Images over text? Overlays. “Virtual text” like in whitespace-mode? Overlays. Inline results of code evaluation: overlays. Multiple cursors? Those are overlays. Emacs has only one cursor.

Making overlays

For a quick look into the overlay API, run M-x shortdoc overlay.

The even shorter version: We can create an overlay ov from buffer position BEG to END with

(setq ov (make-overlay BEG END))

and move it around with

(move-overlay ov NEW-BEG NEW-END)

To avoid creating garbage, it is preferable to reuse overlays when possible instead of throwing them away with (delete-overlay ov) and starting over. move-overlay can move them to other buffers too.

The only other tricky question is what happens to the overlay when text is inserted at its left or right boundaries. Does it expand to contain the text? This is left as an exercise for the reader; perhaps start with M-x describe-function make-overlay?

These alterations are specified by adding or modifying the overlay’s “properties”. Like symbol properties, overlay properties are a way to store arbitrary information along with the overlay, and org-latex-preview uses it to keep track of state, such as whether the preview image is out of date because the LaTeX fragment has been edited, and to store LaTeX compilation errors for the corresponding fragment.

Overlay properties

To set and retrieve overlay properties, we can use

(overlay-put ov prop value)
(overlay-get ov prop)

where the property prop is any elisp symbol. There is also an overlay-properties function to access the full list. Once again, M-x shortdoc overlay has us covered.

Some properties have special meanings as interpreted by Emacs’ display engine. display, the most common property, changes how the overlay region appears on screen, and this is typically set to the image we want to see in place of the text.

But several more interesting modifications are possible with overlays, such as changing the face (font+style) of the underlying text, making it invisible or “intangible” to the cursor, or specifying keymaps that are activated when the cursor is within the bounds of the overlay.

Fun with overlay properties

The only other detail we need is the list of overlay properties that Emacs considers special. The display property can be used to show an image instead of the text “below” (or “under”) the overlay, for example:

(let ((img (create-image "/path/to/image.png")))
  (overlay-put ov 'display img))

The face property can change the display of the text. Here we dim the text the overlay covers:

(overlay-put ov 'face 'shadow)

Here are a few more interesting uses of other special overlay properties.


Send a message if the text under the overlay changes:

(defun notify-on-ov-change (ov after-p beg end &optional _)
   (when after-p
     (message "Text changed: %s"
              (buffer-substring-no-properties beg end))))

(overlay-put ov 'modification-hooks (list #'notify-on-ov-change))

You can use this to set up a “watcher” – perhaps run a process when a region inside a buffer changes!5 This is not hypothetical: the preview-latex package actually does this to recompile LaTeX preambles of TeX files (in the background) as you edit them.


Add a highlight and tooltip (“hover text”) when the mouse is on the overlay:

(overlay-put ov 'mouse-face 'highlight)
(overlay-put ov 'help-echo "You're on an overlay, pal.")

Open the displayed image externally when pressing RET with the cursor on it:

(overlay-put
 ov 'keymap
 (define-keymap "RET"
   (lambda () (interactive)
     (when-let* ((img (get-char-property (point) 'display))
                 ((imagep img))
                 (file (image-property img :file)))
       (start-process "view-image" nil
                      "xdg-open" file)))))

This works by defining a keymap overlay property with the required function. (On non-Linux systems, you will want to change "xdg-open" to an equivalent command-line utility.)


Center an image in a window:

(let* ((img (image-create "/path/to/image.png"))
       (spacing (propertize
                 " " 'face 'default
                 'display
                 `(space :align-to (- center (0.5 . ,img))))))
  (overlay-put ov 'display img)
  (overlay-put ov 'before-string spacing))

This image stays centered as you resize the window:

This one needs a little explaining: The before-string overlay property displays an object before the overlay itself. The object we choose to display is some propertized text with a display text-property. The display text-property provides the means to insert spaces of specified widths, and a symbolic way to refer to the text margins, window center and horizontal window boundary positions. Combining the two gives us (consistently) centered images in Emacs.

In the org-latex-preview library, overlays are the primary UI concept and

The structure of org-latex-preview

Assuming you have a handle on what org-latex-preview does, we can start peering into the how.

How it works: control flow

Here is a high-level overview of the control flow of LaTeX preview generation:

Figure 2: Control flow for Org mode&rsquo;s LaTeX preview system.

Figure 2: Control flow for Org mode’s LaTeX preview system.

This is a rough picture of how LaTeX previews are generated in Org mode, subdivided into tasks The made-up term “task” corresponds to a handful of functions devoted, in our heads, to one purpose. . Each action belongs to one of three categories:

  • Blocking tasks: These must run synchronously and thus require the user to wait while Emacs is unresponsive. We want this to be minimal and/or done ahead of time.
  • Delayable tasks: These tasks can be delayed if more immediate actions – such as accepting user input – are pending. However, once such a task is started it runs synchronously and we’re locked in. We want this work to be chunked into small independent pieces.
  • Asynchronous tasks: These are subprocesses spawned by Emacs and managed by the OS. Emacs monitors the process status and handles process output in a delayable fashion, but is otherwise unencumbered by them.

When a LaTeX preview command is issued, the first chunk of processing (the PREPARE block)

  1. sets up overlays in the buffer that “cover” the fragments, with the intent to add preview images to them at the end.
  2. It creates a TeX file containing the relevant LaTeX fragments from the Org buffer, along with a preamble. This preamble is optionally precompiled, which can speed up processing considerably.

It then starts a two step process chain (the RUN block) that runs asynchronously. The end result of a successful run is a preview image or series of images.

The PROCESS FILTERS block: From this point on until either process ends, Emacs checks in periodically through the associated process filter, acting on the STDOUT to capture various bits of preview metadata. It also keeps track of images that have finished processing, and (synchronously) caches them and adds them to the overlays in the buffer for these fragments. This continuous update is doubly useful:

  • Previews start appearing in the buffer much faster, with the window viewport being updated first.
  • This processing happens in small chunks instead of all at once at the end, so Emacs stays more (even if not fully) responsive to input.

The POSTPROCESS block: After the subprocesses have finished, Emacs runs their process sentinels, which clean up the detritus of this process chain, or clean up the buffer if the preview run failed.

And speaking of detritus…

How it works: file I/O

This figure shows the same process but with an emphasis on disk I/O. The primary chain is TeX fileDVI/PDF output fileSVG/PNG preview images.

Of the four categories of files involved in the process, we are interested in two: the “format dump file”, which is the output of precompilation, and the output preview images. Both are cached and persisted across Emacs sessions. All files as produced are trashed by the POSTPROCESS block.

How it works: data flow

Displaying previews of fragments in a color, alignment and size-aware way along with associated information like errors requires a considerable amount of bookkeeping. Since this information arrives piecemeal and processing happens asynchronously in chunks, we also need some way of storing and passing along this context. There are perhaps clever ways to do this, but we use a straighforward solution – a (dynamic) global alist that associates each active process chain with its data (see org-async below).

All of the data generated and required for the many tasks in our process chain is captured in a single mutable container, referred to here – for convenience – as “Extended Info” (extended-info). This data includes

  • User preferences for the preview run, such as the LaTeX compiler in use,
  • Input LaTeX strings to be rendered,
  • Input file paths, output image paths and image cache details,
  • Emacs’ context like the current buffer – which is important since we need to restore it before we can run asynchronous callbacks,
  • Overlay positions and properties,
  • Compilation errors,
  • Computed preview geometry and fontsize for alignment,

and more besides.

extended-info: implementation

“Extended Info” is implemented as a plist (property list). Actually, almost every compound data structure org-latex-preview uses is either an alist or a plist, which is just a linked-list under the hood. extended-info is also a tree since a few of its members are plists themselves (see below).

We used property lists at first since they’re mutable and amorphous and the design of the library was very fluid. Unfortunately we are now well past the point where we can switch to something more structured like cl-defstruct.

If it’s any consolation, most of Org works with large property lists too. Org’s export system passes around a big plist bucket for communication in much the same way we do here. Access can get expensive as they grow, but at least passing them around is cheap. It’s… not much of a consolation.

apply yourself: property lists as arguments

plists have one other coincidental(?) advantage: You can splice them in as arguments to a cl-defun.

Consider:

(cl-defun foo (&key (buf (current-buffer)) (valid-p t) (pred #'ignore)
                    &allow-other-keys)
  ;; body of foo
  )

This function has the signature

(foo :buf some-buffer :valid-p t :pred #'always)

which is just a list with the name of the function followed by… a plist. So we can build up the plist programmatically and use it directly as foo’s argument list:

(let ((arglist `(:buf ,some-buffer :valid-p t :pred ,#'always :unknown-key nil)))
  (apply #'foo arglist))

’tis a silly thing but oh, so satisfying. In fact, this is a convenient way to feed a job spec to org-async (see below).

How extended-info is populated

The set up functions in the PREPARE block initialize this bucket with data from parsing the buffer – i.e. with LaTeX fragment locations, equation numbers etc. From then on, each task updates extended-info with progressively more information. The structure of extended-info and this flow of information into it is visualized in this “data flow” diagram:

Contained in “Extended Info”: “Processing Info” contains information about and relevant to the external process (LaTeX and the image converter). “Fragment” is a list of metadata for fragments to be previewed, including the overlays that will eventually display the preview images. All of this preview metadata is obtained from the outputs of the latex and image converter runs via the process filters.

Note that Emacs’ interpreter lock precludes data races and other concurrency issues, since only one function modifies extended-info at a time.

How extended-info is accessed

The above chart only captures the flow of data in one direction: from the sources of computation into the extended-info container. Tasks that don’t modify extended-info still need to read it to do their jobs. For example,

  • Clean up temporary files needs the paths to the temporary files.
  • Cache images and metadata needs the generated image paths, as well as all the geometry metadata.

Accessing it is simple: most functions in the process chain are passed extended-info, or a subset thereof, as an argument.

Once again, we get away with it because no concurrency implies no concurrency problems!

The pieces

This is where we get into the weeds. If you’re here for the bird’s eye view, you can skip ahead to live previews now.

Parsing

This is the Collect LaTeX Fragments task in the control flow diagram.

Org’s syntax is quite extensive, and parsing Org files used to involve a giant mess of regular expressions. Thankfully the mess is now handled by the org-element library, which provides a simple and powerful API for querying or acting on the Org AST. A fair amount of work has gone into making it perform well under the constraints of Emacs’ single-threaded event loop.

Crucially for us, Org has first-class support for LaTeX environments and fragments, and this greatly simplifies metadata handling This support is what makes using LaTeX in Org a pleasant experience in the first place. .

The org-element library

Added in late 2021, the org-element library greatly simplifies the process of working with Org.

I have a minimal working knowledge of the library, so this block covers only the aspects of org-element that we use in org-latex-preview.

Org distinguishes between

Every node in the AST as parsed by org-element is made available as a plist whose keys are properties of the element. These properties include the starting and ending positions of the element and its contents (:start, :end and :contents), as well as more

Finding LaTeX

In its current form, org-latex-preview does not simply obtain a list of LaTeX environments and fragments in the buffer by querying the AST:

(org-element-map
    (org-element-parse-buffer)
    '(latex-environment latex-fragment)
  #'identity)

Instead, it uses a regexp scan of the buffer to find the LaTeX regions and org-element to gather specific metadata. This is mainly for performance reasons.

Preamble generation

This is the Create LaTeX preamble task in the control flow diagram.

The org-export (ox) library

Precompilation

This is the Precompile LaTeX preamble task in the control flow diagram.

Creating the TeX file

This is the Create TeX file task in the control flow diagram.

The generated TeX file contains, as you might expect, the preamble and a listing of all the fragments from the Org buffer. Instead of placing them into the buffer with separated with \pagebreak, we place them inside \begin{preview} and \end{preview} blocks provided by the preview.sty CTAN package.

preview.sty is authored by David Kastrup, who also wrote and maintains the Emacs-side preview.el package that’s part of AucTeX. preview.el was the inspiration for org-latex-preview – it was clear at least that previews in Org mode could be much, much faster.

The preview.sty package does three critical things for us:

  1. It places each preview snippet (enclosed within \begin{preview} and \end{preview} blocks) on a separate page in the LaTeX output,
  2. it finds the boundaries of the generated text and crops the page down to them,
  3. and it reports the font size, image size and position relative to the baseline in the LaTeX process’ output stream.

These modifications were technically doable through a combination of other packages (such as tightpage, which Org used before), but in cumbersome and error-prone ways.

This makes preview.sty a dependency of org-latex-preview. Fortunately this package is included by default even with minimal installs of all common LaTeX distributions.

The process runner

The org-async library

This is as good a time as any to talk about org-async.

Because we need to run a chain of processes with different callback behaviors depending on the exit statuses, org-latex-preview uses org-async, a process queue manager that wraps Emacs’ start-process, process filter and process sentinel API. It handles a lot of the bookkeeping for us, providing for

  • declarative specification of a tree of processes that branch on the exit status of a process,
  • including the ability to register multiple callbacks as required,
  • keeping track of the context (the extended-info bucket in our case) and passing it to the filter and sentinel,
  • and limiting the pool of concurrent subprocesses and handling timeouts.

The global variable that keeps track of the context data between async calls is managed by org-async.


The following code might be out of date, please refer to the Org manual.

An example of an org-async job spec might look like the following (simplified for clarity):

(setq task-spec
      (list 'org-async-task             ; <-- LaTeX compilation task
            '("latex" "-interaction" "nonstopmode" "texfile.tex")
            :buffer t
            :info extended-info
            :filter #'latex-preview-filter
            :failure #'latex-failure-callback
            :success (list 'org-async-task ; <-- Image conversion task
                           '("dvisvgm" "--page=1-" "-o out-%p.svg" "texfile.dvi")
                           :buffer t
                           :info extended-info
                           :filter #'dvisvgm-place-previews-filter
                           :failure (list #'dvisvgm-failure-callback
                                          #'log-errors-callback
                                          #'cleanup-callback)
                           :success (list #'check-fragments
                                          #'cleanup-callabck))))

Starting this process chain is then simple:

(org-async-call task-spec)

org-async-call provides many other conveniences for subprocess management.

The latex run

The Image converter run

org-latex-preview supports PNG and SVG previews. For the former, there are two options again:

Sample image converter output streams
pre-processing DVI file (format version 2)
processing page 1
  applying bounding box set by preview package (version 13.1)
  width=346.003754pt, height=15.362961pt, depth=.501877pt
  graphic size: 346.003754pt x 15.864838pt (121.606411mm x 5.575853mm)
  output written to /tmp/org-tex-ang3Se-000000001.svg
1 of 1 page converted in 0.16035 seconds
[1 (preview-latex version 13.1) (preview-latex tightpage option detected, will use its bounding box) depth=1 height=29]

Collecting metadata

The process filters

This is the PROCESS FILTERS block in the control flow diagram.

Sample LaTeX output with preview.sty
Preview: Fontsize 10pt
! Preview: Snippet 1 started.
<-><->

l.9 \begin{preview}

Preview: Tightpage -32891 -32891 32891 32891
! Preview: Snippet 1 ended.(973936+0x22609920).
<-><->

l.14 \end{preview}

The printed information is cryptic. Here’s what the numbers mean.

  • The “tightpage” line reports margins around the rendered equation:
                             Margins:
                             left  bottom  right    top
        Preview: Tightpage -32891  -32891  32891  32891
    
    The unit is “Base pts”, which is 1:65536 of a pt. (There is an additional factor of 72:72.27 on top relating a Base pt to a scaled point.)
  • The next line reports the width, depth (below baseline) and height (from baseline) of the image.
                                    height     depth    width
        ! Preview: Snippet 1 ended.(973936  +  0     x  22609920).
    
  • Finally, any compilation errors reported for each preview in the above output (none here) are captured as well.
    • Errors are reported on lines starting with “lN”, where N is the line number.
    • Messages are reported (as above) on lines starting with “!”.

The preview.sty manual provides more details and documents other options.

Fine-tuning Emacs’ process interaction parameters

Caching

This is the Cache images and metadata task in the control flow diagram.

Displaying images in Emacs

This covers the create overlays and place LaTeX previews blocks in the control flow diagram.

Odds and ends

Displaying compile errors

Fontification

This is Emacs-speak for coloring and styling text.

Live previews: Hooks galore

LaTeX previews everywhere in Emacs

A proof of concept: the design of org-latex-preview makes it technically possible to preview LaTeX anywhere in Emacs. Here’s an example previewing LaTeX formatted comments in a code (Clojure) buffer:

And here’s LaTeX previews in the Emacs’ calculator:

This feature does not exist – yet – but can be added fairly easily. To see why, let’s cast our eyes at the preview process breakdown again. All tasks here are Org-mode-agnostic save for two:

  • Collect LaTeX fragments: org-latex-preview uses org-element,
  • Create preamble: The org-export library parses the buffer, collects

To use LaTeX previews outside of Org mode, we only need providers for these two tasks:

  • A parser that can identify the boundaries of LaTeX fragments in the buffer Technically you could reuse org-element in non-Org buffers for this, but there will be many false negatives and occasional false positives. . This does not need to be a full-fledged parser – just a regexp scan of the buffer looking for \begin{ or \[ can suffice. This is how the above demos were created.

  • A LaTeX preamble generator appropriate for the buffer. This can just be a constant string (instead of a function) that includes a reasonable set of LaTeX packages. I used Org’s default LaTeX preamble string (the variable org-latex-preview-preamble) for the above demos.

The Emacs-wide API for org-latex-preview

This description might be out of date, please refer to the Org manual.

org-latex-preview tries to separate these independent concerns by providing the function org-latex-preview-place, that generates previews anywhere in Emacs given the above information. Previewing LaTeX fragments is as simple as:

(require 'org-latex-preview)

(org-latex-preview-place 'dvisvgm entries nil preamble)

where entries encodes the start and end positions of the fragments, and preamble is the LaTeX preamble to use.

;; entries is a list of lists
;; Each entry is (start end),
;; or (start end contents)
((1342 1388)
 (1911 2104)
 (2273 2295 "\\( \\epsilon + 3 \\)"))
;; preamble is a preamble string
;; See `org-latex-preview-preamble'
"\\documentclass{article}
 [DEFAULT-PACKAGES]
 [PACKAGES]
 \\usepackage{xcolor}"

The positions in entries would be obtained from your parser for the major mode. For preamble you can just reuse or tweak org-latex-preview-preamble in a pinch. See the function documentation for more details.

Requiring the org-latex-preview library will in-turn load a few Org libraries (org-element, org-persist, ox and org-macs), but it does not load all of Org. We tried hard to keep from loading the 16,000 line behemoth that is org.el.

This covers basic (static) preview generation. Live-updating previews in non-Org buffers are harder to pull off and will need to work with org-element. This is unlikely to be a seamless affair.

Other concerns

xelatex, lualatex support

imagemagick support

Backwards compatiblity

HTML and ODT exports


  1. Or as asynchronous as Emacs in its current form allows for. ↩︎

  2. It’s also a wasteful use of computing power. ↩︎

  3. Its shared global state also confers a number of advantages, as we will see. ↩︎

  4. This is to keep your C-g from doing something unpredictable to “background” code that you didn’t initiate. ↩︎

  5. If you want to set up a watcher on the contents of the entire buffer as opposed to a piece of it, you should avoid overlays and look at the hook after-change-functions instead. Be warned: it’s easy to slow down Emacs to a crawl this way if you’re not careful. ↩︎