Wrapping figures in pandoc pdfs

In general, I like to write my pandoc documents as close to the canonical format as possible and let LaTeX deal with positioning figures. It works pretty well, and it’s infinitely more straightforward than doing it in Word. However, LaTeX doesn’t natively wrap text around figures, and sometimes you really need to maximize the use of space. In pure LaTeX, this can be done with the wrapfig package. In pandoc, this package can be used through a template, but it’s a little tricky if you don’t want every figure treated the same way.

I recently wanted to start using pandoc in writing proposals, some of which have hard page limits. I didn’t come across a solution online, and so I worked out a simple little filter, pandoc-wrapfig, that integrates pandoc and the wrapfig package together:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""Pandoc filter to allow variable wrapping of LaTeX/pdf documents
through the wrapfig package.
Simply add a " {?}" tag to the end of the caption for the figure, where
? is an integer specifying the width of the wrap in inches. 0 will 
cause the width of the figure to be used.
"""

from pandocfilters import toJSONFilter, Image, RawInline, stringify
import re

FLAG_PAT = re.compile('.*\{(\d+\.?\d?)\}')

def wrapfig(key, val, fmt, meta):
    if key == 'Image':
        attrs, caption, target = val
        if FLAG_PAT.match(stringify(caption)):
            # Strip tag
            size = FLAG_PAT.match(caption[-1]['c']).group(1)
            stripped_caption = caption[:-2]
            if fmt == 'latex':
                latex_begin = r'\begin{wrapfigure}{r}{' + size + 'in}'
                if len(stripped_caption) > 0:
                    latex_fig = r'\centering\includegraphics{' + target[0] \
                                + '}\caption{'
                    latex_end = r'}\end{wrapfigure}'
                    return [RawInline(fmt, latex_begin + latex_fig)] \
                            + stripped_caption + [RawInline(fmt, latex_end)]
                else:
                    latex_fig = r'\centering\includegraphics{' + target[0] \
                                + '}'
                    latex_end = r'\end{wrapfigure}'
                    return [RawInline(fmt, latex_begin + latex_fig)] \
                            + [RawInline(fmt, latex_end)]
            else:
                return Image(attrs, stripped_caption, target)
        

if __name__ == '__main__':
    toJSONFilter(wrapfig)

The usage of this filter is quite simple: It’s triggered by ending the caption with “{x}”, where x is a number that specifies the width in inches; if x = 0, the text is wrapped to the width of the figure. I’ve set it to align all figures to the right margin; that could easily be changed in the filter (or it could be further modified to enable additional control). Of course, the pdf template must load the wrapfig package. If pandoc is used to generate something other than a pdf, the filter just removes the tag (handy for using Marked).

Here’s a quick example:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

![Figure Caption. {0}](testfig)

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

![Figure Caption.](testfig)

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

The first figure is wrapped to its width, as you’d expect. The second, which lacks the “{x}” tag, is ignored by the filter and inserted without wrapping (useful if the figure is too wide to make enough room for text). Here’s the output using the little modified template I included in the Github package:

Sample document generated using pandoc and the pandoc-wrapfig filter.

Sample document generated using pandoc and the pandoc-wrapfig filter.

Honestly, it’s not seamless. There’s a certain amount of tweaking that one must do to get all the figures into the right positions: they can’t be too close together, nor can they be too close to the end of a page. However, in the end it works well for the right project.

Leave a Reply

Your email address will not be published.