diff --git a/NEWS.md b/NEWS.md index 631aa170c2..292ec1f522 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ - `kable()` supports generating multiple tables from a list of data objects, and the tables will be placed side by side when the output format is HTML or LaTeX +- added a new function `include_graphics()` to embed existing image files into **knitr** documents; see `?include_graphics` for details + - added a chunk option `ffmpeg.bitrate` (default: `1M`) to control the quality of WebM videos created from FFmpeg (thanks, @JsonPunyon, #1090) - added a chunk option `ffmpeg.format` (default: `webm`) to control the video format of FFmpeg; this option specifies the filename extension of the video (#712) diff --git a/R/output.R b/R/output.R index b6a8d1b2a0..6054e561bd 100644 --- a/R/output.R +++ b/R/output.R @@ -539,6 +539,16 @@ wrap.recordedplot = function(x, options) { knit_hooks$get('plot')(file, reduce_plot_opts(options)) } +#' @export +wrap.knit_image_paths = function(x, options, inline = FALSE) { + hook_plot = knit_hooks$get('plot') + options$fig.num = length(x) + paste(unlist(lapply(seq_along(x), function(i) { + options$fig.cur = i + hook_plot(x[i], options) + })), collapse = '') +} + #' A custom printing function #' #' The S3 generic function \code{knit_print} is the default printing function in diff --git a/R/plot.R b/R/plot.R index 8f7e4314fc..e2c33ed979 100644 --- a/R/plot.R +++ b/R/plot.R @@ -319,3 +319,36 @@ plot_crop = function(x, quiet = !opts_knit$get('progress')) { showtext = function(show) { if (isTRUE(show)) getFromNamespace('showtext.begin', 'showtext')() } + +#' Embed external images in \pkg{knitr} documents +#' +#' When plots are not generated from R code, there is no way for \pkg{knitr} to +#' capture plots automatically. In this case, you may generate the images +#' manually and pass their file paths to this function to include them in the +#' output. The major advantage of using this function is that it is portable in +#' the sense that it works for all document formats that \pkg{knitr} supports, +#' so you do not need to think if you have to use, for example, LaTeX or +#' Markdown syntax, to embed an external image. Chunk options related to +#' graphics output that work for normal R plots also work for these images, such +#' as \code{out.width} and \code{out.height}. +#' @param path a character vector of image paths +#' @param auto_pdf whether to use PDF images automatically when the output +#' format is LaTeX, e.g. \file{foo/bar.png} will be replaced by +#' \file{foo/bar.pdf} if the latter exists; this can be useful since normally +#' PDF images are of higher qualities than raster images like PNG when the +#' output is LaTeX/PDF +#' @note This function is supposed to be used in R code chunks or inline R code +#' expressions. You are recommended to use forward slashes (\verb{/}) as path +#' separators instead of backslashes in the image paths. +#' @return The same as the input character vector \code{path} but it is marked +#' with special internal S3 classes so that \pkg{knitr} will convert the file +#' paths to proper output code according to the output format. +#' @export +include_graphics = function(path, auto_pdf = TRUE) { + if (auto_pdf && is_latex_output()) { + path2 = sub_ext(path, 'pdf') + i = file.exists(path2) + path[i] = path2[i] + } + structure(path, class = c('knit_image_paths', 'knit_asis')) +} diff --git a/tests/testit/test-hooks.R b/tests/testit/test-hooks.R index 1653b5a61c..8e975cf636 100644 --- a/tests/testit/test-hooks.R +++ b/tests/testit/test-hooks.R @@ -16,4 +16,19 @@ assert( ) ) +render_markdown() + +img_output = function(path, opts = list()) { + opts = opts_chunk$merge(opts) + wrap(knit_print(include_graphics(path)), opts) +} + +assert( + 'include_graphics() includes custom images correctly', + identical(img_output('a.png'), '![](a.png) '), + identical(img_output(c('a.png', 'b.png')), '![](a.png) ![](b.png) '), + identical(img_output('a.png', list(fig.cap = 'foo bar')), '![foo bar](a.png) '), + identical(img_output('a.png', list(out.width = '50%')), '') +) + knit_hooks$restore()