Better equation numbering in LaTeX fragments in org-mode

| categories: emacs, orgmode, latex | tags:

In org-mode we can use LaTeX equations, and toggle an overlay that shows what the rendered equation will look like. One thing that has always bothered me though, is that each fragment is created in isolation. That means numbering is almost always wrong, and typically with each numbered equation starting with (1). Here we look at a way to fix that. Fixing it means we have to find a way to not create each fragment image in isolation; each one needs a context that enables the numbering to be correct. The idea we try here is simple: we just figure out in advance what the numbering for each equation should be, and then figure out how to get that information to the image generation.

See this video of the post in action:

Here are some example equations to see how it works.

This should be numbered (1)

\begin{equation} \int x^2 dx \end{equation}

This is a numbered equation with a custom number. This should have (E1) as the number.

\begin{equation}\tag{E1} \int x^2 dx \end{equation}

But equation* is not numbered

\begin{equation*} \int x^2 dx \end{equation*}

LaTeX align environments are numbered. The first line is (2), the second line is not numbered (because we put \nonumber in the line), and the third line is (3).

\begin{align} a = 5 \\ b=6 \nonumber \\ c = 8 \end{align}

But align* environments are not numbered.

\begin{align*} a = 5 \\ b=6 \end{align*}

This should be numbered (4).

\begin{equation} \int x^3 dx \end{equation}

These should be numbered (5), (6) and (7).

\begin{align} a = 5 \\ b=6 \\ c = 8 \end{align}

This should be numbered with (E2).

\begin{equation}\tag{E2} \int x^2 dx \end{equation}

And this should be numbered (8).

\begin{equation} \int x^2 dx \end{equation}

Note: This will be numbered (1) because it is exactly the same equation as a previous one!

\begin{equation} \int x^2 dx \end{equation}

We can change the numbering of an equation with code like this. After this code, the next equation will be numbered (5).

The only fragments that should be numbered are equation environments, and align environments (these are the main ones that we consider here). The align environment is tricky since there is potentially more than one number in the environment.

So, we get all the fragments, and generate a list of which ones should be numbered, and if they should what the number should be. That means we will need to count the number of numbered equations in an align environment. We will do that by getting the number of line breaks, and subtracting the number of nonumbers.

Here is the code block that does that, using advice again. A downside of this approach is that we generate the list for every fragment, which is not efficient, since it should not change in a synchronous approach to generating them.

(defun org-renumber-environment (orig-func &rest args)
  (let ((results '()) 
        (counter -1)
        (numberp))

    (setq results (loop for (begin .  env) in 
                        (org-element-map (org-element-parse-buffer) 'latex-environment
                          (lambda (env)
                            (cons
                             (org-element-property :begin env)
                             (org-element-property :value env))))
                        collect
                        (cond
                         ((and (string-match "\\\\begin{equation}" env)
                               (not (string-match "\\\\tag{" env)))
                          (incf counter)
                          (cons begin counter))
                         ((string-match "\\\\begin{align}" env)
                          (prog2
                              (incf counter)
                              (cons begin counter)                          
                            (with-temp-buffer
                              (insert env)
                              (goto-char (point-min))
                              ;; \\ is used for a new line. Each one leads to a number
                              (incf counter (count-matches "\\\\$"))
                              ;; unless there are nonumbers.
                              (goto-char (point-min))
                              (decf counter (count-matches "\\nonumber")))))
                         (t
                          (cons begin nil)))))

    (when (setq numberp (cdr (assoc (point) results)))
      (setf (car args)
            (concat
             (format "\\setcounter{equation}{%s}\n" numberp)
             (car args)))))
  
  (apply orig-func args))

(advice-add 'org-create-formula-image :around #'org-renumber-environment)

You can remove the advice like this.

(advice-remove 'org-create-formula-image #'org-renumber-environment)

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter

Justifying LaTeX preview fragments in org-mode

| categories: emacs, orgmode, latex | tags:

A colleague asked if I knew how to center the preview images of LaTeX equations in an org-buffer. This might make org-mode notes look nicer when lecturing, for example. We thought it might be possible to just offset the overlay with a before-string made up of the right number of spaces. I worked out a full solution that lets you "justify" the preview images. You have to add a :justify option to org-format-latex-options, and the option is either 'center or 'right (anything else means left-justified as the default). This will only justify equations that start at the beginning of a line to avoid modifying fragments that are in text. You should see the video to see this in action:

Equation 1: \(e^{i\pi} + 1 = 0\)

An \(x^2 = -1\) equation in the text is not affected.

A display equation with some space after the equation: \[e^{i \cdot \pi} + 1 = 0\]

This is a numbered equation.

\begin{equation} \int x^2 dx \end{equation}

The idea is pretty simple, we get the width of the window, and the width of the image, and compute the offset that approximately centers or right justifies the overlay, and then add the before-string property to the overlay. While we are at it, I will add a tooltip to the image so you can see the LaTeX code that created it, and make it clickable so you can toggle it back to the code. I apply the functions as after advice to the function that creates the overlay, so we do not have to adapt the org code at all. Here is the code that does it.

;; specify the justification you want
(plist-put org-format-latex-options :justify 'center)

(defun org-justify-fragment-overlay (beg end image imagetype)
  "Adjust the justification of a LaTeX fragment.
The justification is set by :justify in
`org-format-latex-options'. Only equations at the beginning of a
line are justified."
  (cond
   ;; Centered justification
   ((and (eq 'center (plist-get org-format-latex-options :justify)) 
         (= beg (line-beginning-position)))
    (let* ((img (create-image image 'imagemagick t))
           (width (car (image-size img)))
           (offset (floor (- (/ (window-text-width) 2) (/ width 2)))))
      (overlay-put (ov-at) 'before-string (make-string offset ? ))))
   ;; Right justification
   ((and (eq 'right (plist-get org-format-latex-options :justify)) 
         (= beg (line-beginning-position)))
    (let* ((img (create-image image 'imagemagick t))
           (width (car (image-display-size (overlay-get (ov-at) 'display))))
           (offset (floor (- (window-text-width) width (- (line-end-position) end)))))
      (overlay-put (ov-at) 'before-string (make-string offset ? ))))))

(defun org-latex-fragment-tooltip (beg end image imagetype)
  "Add the fragment tooltip to the overlay and set click function to toggle it."
  (overlay-put (ov-at) 'help-echo
               (concat (buffer-substring beg end)
                       "mouse-1 to toggle."))
  (overlay-put (ov-at) 'local-map (let ((map (make-sparse-keymap)))
                                    (define-key map [mouse-1]
                                      `(lambda ()
                                         (interactive)
                                         (org-remove-latex-fragment-image-overlays ,beg ,end)))
                                    map)))

;; advise the function to a
(advice-add 'org--format-latex-make-overlay :after 'org-justify-fragment-overlay)
(advice-add 'org--format-latex-make-overlay :after 'org-latex-fragment-tooltip)

That is it. If you get tired of the advice, remove it like this:

(advice-remove 'org--format-latex-make-overlay 'org-justify-fragment-overlay)
(advice-remove 'org--format-latex-make-overlay 'org-latex-fragment-tooltip)

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter