knitrrnw

How to use different "dev.off()" with knitr (to auto-crop figures)


I would like to use my own pdf() plot device in an .Rnw document converted to a PDF with knitr. After the PDF of a figure is generated it should call pdfCrop.off() instead of dev.off() (or whatever knitr calls); this would perfectly crop the resulting figures. How can this be done?

The following MWE works (but without cropping) if (*) is commented out (and the line before properly closed).

\documentclass{article}

\begin{document}
<<knitr_options, echo = FALSE, results = "hide", purl = FALSE>>=
## Custom graphics device (for cropping .pdf):
pdfCrop <- function(file, width, height, ...)
{
    f <- file
    grDevices::pdf(f, width = width, height = height, onefile = FALSE)
    assign(".pdfCrop.file", f, envir = globalenv())
}
pdfCrop.off <- function() # used automagically
{
    grDevices::dev.off() # closing the pdf device
    f <- get(".pdfCrop.file", envir = globalenv())
    system(paste("pdfcrop --pdftexcmd pdftex", f, f, "1>/dev/null 2>&1"),
           intern = FALSE) # crop the file (relies on PATH)
}

## knitr options
knitr::opts_chunk$set(fig.path = "./fig_", background = "#FFFFFF",
                      dev = "pdfCrop", fig.ext = "pdf") # (*) => how to use pdfCrop.off() instead of dev.off()?
@

<<MWE>>=
<<fig-MWE, eval = FALSE, echo = FALSE>>=
plot(1:10, 10:1)
@
\setkeys{Gin}{width=\textwidth}
\begin{figure}[htbp]
  \centering
  \framebox{
<<figMWE, echo = FALSE, fig.width=6, fig.height=6>>=
<<fig-MWE>>
@
}
\caption{Just some text to show the actual textwidth in order to see that the
  figure is not perfectly horizontally aligned due to some white space which can
  be avoided by properly crop the figure with an adequate pdf crop device.}
\end{figure}

\end{document}

Solution

  • knitr already provides a crop device based on pdfcrop, so we can use that via a hook:

    \documentclass{article}
    
    \begin{document}
    <<knitr_options, echo = FALSE, results = "hide", purl = FALSE>>=
    ## knitr options
    library(knitr)
    knit_hooks$set(crop = hook_pdfcrop)
    knitr::opts_chunk$set(fig.path = "./fig_", # all figures are saved as fig_*
                          background = "#FFFFFF", # avoid color
                          crop = TRUE) # always crop
    @
    
    <<MWE>>=
    <<fig-MWE, eval = FALSE, echo = FALSE>>=
    plot(1:10, 10:1)
    @
    \setkeys{Gin}{width=\textwidth}
    \begin{figure}[htbp]
      \centering
    <<figMWE, echo = FALSE, fig.width=6, fig.height=6>>=
    <<fig-MWE>>
    @
    \caption{Just some text to show the actual textwidth in order to see that the
      figure is not perfectly horizontally aligned due to some white space which can
      be avoided by properly crop the figure with an adequate pdf crop device.}
    \end{figure}
    
    \end{document}