Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default relative path processing in include_graphics() can create broken path #2171

Open
3 tasks done
naikymen opened this issue Sep 17, 2022 · 7 comments
Open
3 tasks done
Assignees
Labels
bug Bugs next Issues/PRs considered for the next release

Comments

@naikymen
Copy link

naikymen commented Sep 17, 2022

Hi!

I'm having difficulties including images during knitting.

Here is a reproducible example: test.zip

The notebook is in R/Rmd1/notebook1.Rmd. If I run the notebook in RStudio everything works.

Knit the document to get the following error:

image

Relevant bits about the example:

  • I usually keep notebooks in subdirectories of the project, tipically under R/.
  • I like to set knitr::opts_knit$set(root.dir = here::here()) at the setup chunk, so paths in chunks are always relative to the project while I'm working.
  • I like to save knitted documents to output/renders (again relative to the project's root) by setting knit in the YAML header to the following function: rmarkdown::render(inputFile, encoding = encoding, output_dir = "output/renders").

If i omit the include_graphics call, Knit works and R is able to find the file with file.exists: notebook1.pdf

If instead of normalizing the path I pass a path relative to the project's root, using knitr::include_graphics("results/image_1.png"). I get a different error:

! Unable to load picture or PDF file 'results/image_1.png'.
<to be read again> 
                   }
l.174 \includegraphics{results/image_1.png}

I would appreciate if someone could point me in the right direction. What path should I pass to include_graphics in this case?

I'm submitting this as a bug report because I have been unable to find out why this happens, everything else seems to work, and it is unexpected behavior from my perspective.

Thank you for all the work you've done :)

Best!

N.


By filing an issue to this repo, I promise that

  • I have fully read the issue guide at https://yihui.org/issue/.
  • I have provided the necessary information about my issue.
    • If I'm asking a question, I have already asked it on Stack Overflow or RStudio Community, waited for at least 24 hours, and included a link to my question there.
    • If I'm filing a bug report, I have included a minimal, self-contained, and reproducible example, and have also included xfun::session_info('knitr'). I have upgraded all my packages to their latest versions (e.g., R, RStudio, and R packages), and also tried the development version: remotes::install_github('yihui/knitr').
    • If I have posted the same issue elsewhere, I have also mentioned it in this issue.
  • I have learned the Github Markdown syntax, and formatted my issue correctly.

I understand that my issue may be closed if I don't fulfill my promises.

Session info:

> xfun::session_info("knitr")
R version 4.2.1 (2022-06-23)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Arch Linux, RStudio 2022.2.0.443

Locale:
  LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=es_AR.UTF-8       
  LC_COLLATE=en_GB.UTF-8     LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
  LC_PAPER=en_GB.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
  LC_TELEPHONE=C             LC_MEASUREMENT=es_AR.UTF-8 LC_IDENTIFICATION=C       

Package version:
  evaluate_0.16   glue_1.6.2      graphics_4.2.1  grDevices_4.2.1 highr_0.9       knitr_1.40     
  magrittr_2.0.3  methods_4.2.1   stats_4.2.1     stringi_1.7.8   stringr_1.4.1   tools_4.2.1    
  utils_4.2.1     xfun_0.33       yaml_2.3.5 
@naikymen naikymen changed the title include_graphics can't find files include_graphics can't find files during Knit Sep 17, 2022
@naikymen
Copy link
Author

naikymen commented Sep 17, 2022

When passing "results/image_1.png" to include_graphics Knit worked, only if I added the image to the notebook's directory: R/Rmd1/results/image_1.png.

I was able to get the normalized path to work by adding the images to the (second) parent directory of the project: ../../results/image_1.png.

I don't understand how to tell include_graphics to look for the image at the project's root directory during knitting: results/image_1.png

@naikymen
Copy link
Author

naikymen commented Sep 17, 2022

Well, I've found a workaround.

Here is an updated example: test3.zip

  • notebook1.Rmd will now Knit and inlcude the image using the absolute path, because I set error=F.
  • notebook1_error.Rmd is identical to notebook1.Rmd, except that error=T is set (the default for include_graphics) and will not Knit.
  • notebook2.Rmd will now Knit and inlcude the image using a path relative to the notebook (../../results/image_1.png), because I set error=F.
  • notebook2_error.Rmd is identical to notebook2.Rmd, except that error=T is set (the default for include_graphics) and will not Knit.
  • Using a path relative to the project (results/image_1.png) will not Knit, regardless of the error setting.

It seems to me that the error is raised mistakenly by include_graphics, because omitting the check results in the correct output. Or perhaps include_graphics is executed with an incorrect working directory.

Does this make sense?

PS: sorry I updated my comments so many times.

@naikymen
Copy link
Author

Possible culpirt:

knitr/R/plot.R

Line 492 in 6bfffe9

if (error && length(p <- path[!xfun::is_web_path(path) & !file.exists(path)])) stop(

@jameelalsalam
Copy link

I have also been struggling with and trying to figure out this issue. I believe that part of the issue is that if you pass an absolute path to knitr::include_graphics that it will try to convert it to a relative path, but it converts it to a path relative to the Rmd file, not relative to the output directory as is should.

This in turn is because knitr::opts_knit$get("output.dir") returns the Rmd file location, not the output directory. This is used inside of include_graphics.

Reprex here:

root_dir <- tempdir()

knitr_output_dir <- "to receive path from knit"

fs::dir_create(root_dir, "rmd-dir")
cat(
"---\ntitle: test\noutput: html_document\n---\n\n```{r}\n

# only important part of Rmd file:
knitr_output_dir <<- knitr::opts_knit$get('output.dir')\n```",
file = fs::path(root_dir, "rmd-dir/test.Rmd"))

fs::dir_create(root_dir, "output")
rmarkdown::render(
  fs::path(root_dir, "rmd-dir/test.Rmd"),
  output_dir = fs::path(root_dir, "output")
)
#> processing file: test.Rmd
#> output file: test.knit.md
#> "C:/Program Files/RStudio/bin/quarto/bin/tools/pandoc" +RTS -K512m -RTS test.knit.md --to html4 --from markdown+autolink_bare_uris+tex_math_single_backslash --output pandoc4d2879d05b93.html --lua-filter "C:\Users\jalsal02\R\win-library\4.1\rmarkdown\rmarkdown\lua\pagebreak.lua" --lua-filter "C:\Users\jalsal02\R\win-library\4.1\rmarkdown\rmarkdown\lua\latex-div.lua" --self-contained --variable bs3=TRUE --section-divs --template "C:\Users\jalsal02\R\win-library\4.1\rmarkdown\rmd\h\default.html" --no-highlight --variable highlightjs=1 --variable theme=bootstrap --mathjax --variable "mathjax-url=https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" --include-in-header "C:\Users\jalsal02\AppData\Local\Temp\RtmpklzUsb\rmarkdown-str4d283a383e3d.html"
#> 
#> Output created: C:/Users/jalsal02/AppData/Local/Temp/RtmpklzUsb/output/test.html

print(knitr_output_dir)
#> [1] "C:/Users/jalsal02/AppData/Local/Temp/RtmpklzUsb/rmd-dir"

Created on 2022-09-21 by the reprex package (v2.0.1)

Session info
sessioninfo::session_info()
#> - Session info ---------------------------------------------------------------
#>  setting  value
#>  version  R version 4.1.3 (2022-03-10)
#>  os       Windows 10 x64 (build 19042)
#>  system   x86_64, mingw32
#>  ui       RTerm
#>  language (EN)
#>  collate  English_United States.1252
#>  ctype    English_United States.1252
#>  tz       America/New_York
#>  date     2022-09-21
#>  pandoc   2.18 @ C:/Program Files/RStudio/bin/quarto/bin/tools/ (via rmarkdown)
#> 
#> - Packages -------------------------------------------------------------------
#>  package     * version date (UTC) lib source
#>  bslib         0.3.1   2021-10-06 [1] CRAN (R 4.1.1)
#>  cli           3.3.0   2022-04-25 [1] CRAN (R 4.1.3)
#>  digest        0.6.29  2021-12-01 [1] CRAN (R 4.1.3)
#>  evaluate      0.15    2022-02-18 [1] CRAN (R 4.1.3)
#>  fastmap       1.1.0   2021-01-25 [1] CRAN (R 4.1.1)
#>  fs            1.5.2   2021-12-08 [1] CRAN (R 4.1.3)
#>  glue          1.6.2   2022-02-24 [1] CRAN (R 4.1.3)
#>  highr         0.9     2021-04-16 [1] CRAN (R 4.1.1)
#>  htmltools     0.5.2   2021-08-25 [1] CRAN (R 4.1.1)
#>  jquerylib     0.1.4   2021-04-26 [1] CRAN (R 4.1.1)
#>  jsonlite      1.8.0   2022-02-22 [1] CRAN (R 4.1.3)
#>  knitr         1.40    2022-08-24 [1] CRAN (R 4.1.3)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.1.3)
#>  R6            2.5.1   2021-08-19 [1] CRAN (R 4.1.1)
#>  reprex        2.0.1   2021-08-05 [1] CRAN (R 4.1.1)
#>  rlang         1.0.5   2022-08-31 [1] CRAN (R 4.1.3)
#>  rmarkdown     2.16    2022-08-24 [1] CRAN (R 4.1.3)
#>  rstudioapi    0.13    2020-11-12 [1] CRAN (R 4.1.1)
#>  sass          0.4.1   2022-03-23 [1] CRAN (R 4.1.3)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.1.3)
#>  stringi       1.7.6   2021-11-29 [1] CRAN (R 4.1.2)
#>  stringr       1.4.0   2019-02-10 [1] CRAN (R 4.1.1)
#>  withr         2.5.0   2022-03-03 [1] CRAN (R 4.1.3)
#>  xfun          0.33    2022-09-12 [1] CRAN (R 4.1.3)
#>  yaml          2.3.5   2022-02-21 [1] CRAN (R 4.1.2)
#> 
#>  [1] C:/Users/jalsal02/R/win-library/4.1
#>  [2] C:/Program Files/R/R-4.1.3/library
#> 
#> ------------------------------------------------------------------------------

@jameelalsalam
Copy link

@naikymen Another approach you should be aware of is to pass absolute path to include_graphics (e.g., what your first example did with here::here()), and then include_graphics(file, rel_path = FALSE) to suppress the behavior to transform absolute into relative path.

cderv referenced this issue in rstudio/rmarkdown Apr 30, 2024
it's unlikely for an img src to contain line breaks, so it shouldn't be necessary to one_string(html) beforehand and split it afterwards
@cderv cderv added the bug Bugs label Apr 30, 2024
@cderv cderv moved this to Todo In Progress in R Markdown Team Projects Apr 30, 2024
@cderv cderv added the next Issues/PRs considered for the next release label Apr 30, 2024
@cderv
Copy link
Collaborator

cderv commented Apr 30, 2024

The issue is here is mixing quite a few path handling and so create some unfound file at the end,

here is the structure of project

> fs::dir_tree()
.
├── output
│   └── renders
├── R
│   └── Rmd1
│       └── notebook1.Rmd
├── results
│   └── image_1.png
└── test.Rproj

Here is the Rmd used in test.zip

---
title: "Test"
output:
  pdf_document:
    latex_engine: xelatex
    toc: true
    toc_depth: 4
    number_sections: true
editor_options:
  chunk_output_type: inline
author: NM
date: "`r format(Sys.time(), '%d %B, %Y')`"
urlcolor: blue
knit: (function(inputFile, encoding) {
  rmarkdown::render(inputFile, encoding = encoding, output_dir = "output/renders") })
---

```{r setup, message=F}
library(here)
library(magrittr)

knitr::opts_chunk$set(message = F)
knitr::opts_knit$set(root.dir = here::here())
```

## Test

### File path

```{r}
file.path <- paste0(
    here::here(),
    "/results/image_1.png"
  )
```

### Test path

```{r}
file.exists(file.path)
```

### Test include

```{r}
file.path %>% 
  normalizePath() %>% 
  knitr::include_graphics()
```
  • Using inlcude_graphics() is best done with relative path. When using absolute path, the function will try to make it relative. This is a default process, and can be opt out with rel_path = FALSE or with knitr.graphics.rel_path.

    • I believe here your issue is with this relative path processing.
    • More below with example, but relative path processing is done from the working directory of knitr which is usually the input folder of the Rmd so Rmd1 folder in your case. Hence the computed ../../results/images.1.png
  • Changing root dir knitr::opts_knit$set(root.dir = here::here()) is always risky, you'll modify the behavior of all chunks. It means here that all chunk will be evaluated in root.dir, so test of existance in include_graphics() will be relative to root dir, and it fails because the relative path created is not correct from there. In your case removing knitr::opts_knit$set(root.dir = here::here()) solves your issue. Setting error = FALSE or knitr.graphics.error = FALSE as option should also solve the problem.

So you can resolve your problem quite easily by configuring the right options, of just using relative path directly with include_graphics() as you call it directly.

Though it is a symptom of a general issue with rel_path = TRUE feature in include_graphics(). I'll post some more details in another post below.

@cderv
Copy link
Collaborator

cderv commented Apr 30, 2024

General issue here is related to current absolute to relative processing in include_graphics()

knitr/R/plot.R

Lines 473 to 479 in 44a7bee

if (any(i <- xfun::is_abs_path(path)) && rel_path && !is.null(d <- opts_knit$get('output.dir'))) {
path[i] = xfun::relative_path(path[i], d, error = FALSE)
if (any(j <- xfun::is_abs_path(path[i]))) warning(
'It is highly recommended to use relative paths for images. ',
'You had absolute paths: ', quote_vec(path[i][j])
)
}

Currently, include_graphics() is considering that an absolute path provided will be relative to knitr::opts_knit$get("output.dir"). It is not made relative to output_dir from rmarkdown::render() which is used for fig.path option set by R Markdown (knitr::fig_path()),

Other examples of this issue

Using magick when rendering with absolute file path from different input / output directory

library(withr)
# Rmd in is one folder (like `./vignettes/`)
rmd <- withr::local_tempfile(fileext = ".Rmd", tmpdir = file.path(tempdir(), "rmd"))
dir.create(dirname(rmd), recursive = TRUE, showWarnings = FALSE)
# Output dir in another folder not a subfolder of the input folder (like pkgdown with ./docs)
output_dir <- withr::local_tempdir("output")
# Using magick example which does 
# - use `knitr::fig_path()` to save the png
# - use `knitr::include_graphics()` to include the png
xfun::write_utf8(
  c(
    "---",
    "title: 'A Minimal Example'",
    "---",
    "",
    "```{r}",
    'frink <- magick::image_read("https://jeroen.github.io/images/frink.png")',
    "frink",
    "```"
  ),
  rmd
)

# Calling with absolute paths to trigger absolute paths handling in knitr
out <- rmarkdown::render(rmd, "html_document", output_options = list(self_contained = FALSE, keep_md = TRUE), output_dir = output_dir, quiet = TRUE)
html <- xml2::read_html(out)
img <- xml2::xml_find_first(html, ".//img")
img
#> {html_node}
#> <img src="../output323029512174/file32303f207cc3_files/figure-html/unnamed-chunk-1-1.png" width="220">

The above is not a valid path in the HTML output - resources won't be matched as the HTML is in the output dir already

withr::with_options(list(knitr.graphics.rel_path = FALSE), {
  out <- rmarkdown::render(rmd, "html_document", output_options = list(self_contained = FALSE, keep_md = TRUE), output_dir = output_dir, quiet = TRUE)
  html <- xml2::read_html(out)
  img <- xml2::xml_find_first(html, ".//img")
  img
})
#> {html_node}
#> <img src="file32303f207cc3_files/figure-html/unnamed-chunk-1-1.png" width="220">

In the latter case, html_document_base() is fixing the path correctly to be relative to output directory

# cleaning examples above
withr::deferred_run()

Using gganimate when rendering with absolute file path from different input and output directory

Same as above as gganimate:::knit_print.gif_image do the same as magick:::`knit_print.magick-image`

@cderv cderv changed the title include_graphics can't find files during Knit default relative path processing in include_graphics() can create broken path Apr 30, 2024
cderv added a commit to cderv/pkgdown that referenced this issue Apr 30, 2024
…ve path processing.

This processing is by default, and paths are made relative to input directory and not output.

It should be fixed in later knitr version yihui/knitr#2171
cderv added a commit to cderv/pkgdown that referenced this issue Apr 30, 2024
…ve path processing.

This processing is by default, and paths are made relative to input directory and not output.

It should be fixed in later knitr version yihui/knitr#2171
hadley added a commit to r-lib/pkgdown that referenced this issue Apr 30, 2024
SebKrantz pushed a commit to SebKrantz/pkgdown that referenced this issue Jun 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bugs next Issues/PRs considered for the next release
Projects
None yet
Development

No branches or pull requests

4 participants