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

Support self-contained images in xaringan slides (close #3) #207

Merged
merged 26 commits into from Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dda23ac
self-contained images for xaringan, with tests
Apr 16, 2019
7040fc1
update news/description/documentation
Apr 16, 2019
db3832e
fix space issues in regex
Apr 16, 2019
080510e
Better error checking - ensure file exists before passing to URI enco…
Apr 16, 2019
2852aa0
add documentation
Apr 16, 2019
1c82537
fix tests, detect file extension, add test of local file as well.
Apr 17, 2019
3c586cb
Fix rebase
Mar 5, 2020
425c2c2
fix names
Mar 5, 2020
f7f6f7c
Fix tests
Mar 5, 2020
b7bc388
Better error checking - ensure file exists before passing to URI enco…
Apr 16, 2019
815594b
add documentation
Apr 16, 2019
bd88cee
handle local files, fix tests
Mar 5, 2020
bc276c6
Merge branch 'master' of https://github.com/srvanderplas/xaringan
Mar 5, 2020
5b52b88
updated documentation, remove tests that were redundant.
Mar 5, 2020
fb35427
Format to 80 chars... only exception is the giant regex
Mar 5, 2020
e61e2e8
revert irrelevant changes
yihui Sep 23, 2020
e96165b
add person() back
yihui Sep 23, 2020
377bec9
adjust order, and not sure why these two ctb's were added
yihui Sep 23, 2020
7b65afa
don't support <img></img> because the img tag shouldn't be closed by …
yihui Sep 23, 2020
143d09e
rename process_self_contained_images to encode_images(), and replace …
yihui Sep 23, 2020
6b2b6b3
the full implementation of self-contained slides: the basic idea is t…
yihui Sep 24, 2020
2287916
factor out a helper function, and a string token
yihui Sep 24, 2020
7af7873
update tests
yihui Sep 24, 2020
d3ce953
update doc
yihui Sep 24, 2020
4ede020
update news
yihui Sep 24, 2020
4066070
p -> P
yihui Sep 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Expand Up @@ -22,7 +22,8 @@ Authors@R: c(
person("Yongfu", "Liao", role = "ctb"),
person("Malcolm", "Barrett", role = "ctb", comment = c(ORCID = "0000-0003-0299-5825")),
person("Alessandro", "Gasparini", role = "ctb", comment = c(ORCID = "0000-0002-8319-7624")),
person("Yue", "Jiang", role = "ctb", comment = c(ORCID = "0000-0002-9798-5517"))
person("Yue", "Jiang", role = "ctb", comment = c(ORCID = "0000-0002-9798-5517")),
person("Susan", "Vanderplas", role = "ctb", comment = c(ORCID = "0000-0002-3803-0972"))
)
Description: Create HTML5 slides with R Markdown and the JavaScript library
'remark.js' (<https://remarkjs.com>).
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Expand Up @@ -4,6 +4,8 @@

- Added the ability to specify an `institute` in the YAML header that is automatically added to the title slide (@paullemmens, #200).

- Added encoding of images specified with markdown/HTML when `self_contained: true`

## BUG FIXES

- The Infinite Moon Reader (`xaringan::inf_mr()`) didn't take slide properties `layout: true` and `exclude: true` into consideration (thanks, @fmmattioni, #198).
Expand Down
12 changes: 8 additions & 4 deletions R/render.R
Expand Up @@ -51,15 +51,16 @@
#' @note Do not stare at Karl's picture for too long after you turn on the
#' \code{yolo} mode. I believe he has Sharingan.
#'
#' Local images that you inserted via the Markdown syntax
#' \command{![](path/to/image)} will not be embedded into the HTML file when
#' \code{self_contained = TRUE} (only CSS, JavaScript, and R plot files will
#' be embedded). You may also download remark.js (via
# You may download remark.js (via
#' \code{\link{summon_remark}()}) and use a local copy instead of the default
#' \code{chakra} argument when \code{self_contained = TRUE}, because it may be
#' time-consuming for Pandoc to download remark.js each time you compile your
#' slides.
#'
#' \code{self_contained: true} will now encode linked files as long as the file name
#' does not contain parentheses, brackets, or <> characters, and as long as
#' local files actually exist.
#'
#' Each page has its own countdown timer (when the option \code{countdown} is
#' set in \code{nature}), and the timer is (re)initialized whenever you
#' navigate to a new page. If you need a global timer, you can use the
Expand Down Expand Up @@ -172,6 +173,9 @@ moon_reader = function(
res = split_yaml_body(input_file)
write_utf8(res$yaml, input_file)
res$body = protect_math(res$body)
if (self_contained) {
res$body = process_self_contained_images(res$body)
}
content = htmlEscape(yolofy(res$body, yolo))
Encoding(content) = 'UTF-8'
write_utf8(content, tmp_md)
Expand Down
64 changes: 64 additions & 0 deletions R/utils.R
Expand Up @@ -205,3 +205,67 @@ process_slide = function(x) {
x = protect_math(flatten_chunk(x))
paste(x, collapse = '\n')
}

process_self_contained_images = function(x) {
# Identify prose lines, don't process further
isprose <- xfun::prose_index(x)
notprose <- setdiff(1:length(x), isprose)
final <- x

# This is the full regular expression.
# \\1 captures the whole tag,
# \\2 captures the file link/url
# image_regex <- "((?:(?:!\\[.*?\\]\\()|(?:<img .*?src=[\'\"])|(?:background-image: url\\())(.*?)(?:(?:\\))|(?:[\'\"].*?/(?:img)?>)|(?:\\))))"

# Here it is broken down into reasonable bits and then reassembled
open_options <- c("!\\[.*?\\]\\(", "<img .*?src ?= ?[\'\"]", "background-image: url\\(", "background-image: ")
close_options <- c("\\)", "[\'\"].*?/(?:img)?>", "\\)", "\\b$")
all_open <- paste0("(?:", paste0("(?:", open_options, ")", collapse = "|"), ")")
all_close <- paste0("(?:", paste0("(?:", close_options, ")", collapse = "|"), ")")
image_regex <- paste0("(", all_open, "(.*?)", all_close, ")")

# This part captures situations where the image specification is enclosed in backticks.
backtick_regex <- paste0("`[^`]*", image_regex, "[^`]*`")

images <- which(grepl(image_regex, x))
not_images <- which(grepl(backtick_regex, x))

# Remove any backtick-images and notprose lines from the set of identified images
images <- setdiff(images, not_images)
images <- setdiff(images, notprose)

if (length(images) > 0) {
# Get a list of file paths or URLs to encode
encode_tag <- regmatches(x[images], gregexpr(image_regex, x[images]))
encode_list <- unique(gsub(image_regex, "\\2", unlist(encode_tag)))
encode_file <- function(y) {
if (grepl("^(https|www|http)", y)) {
ext <- regmatches(y, regexpr("\\.\\w{1,}$", y))
tf <- tempfile(fileext = ext)
download.file(y, tf, mode = "wb", quiet = T)
} else {
tf <- y
}
if (file.exists(tf)) {
knitr::image_uri(tf)
} else {
y
}
}
encoded_list <- sapply(encode_list, encode_file)
encoded_list_worked <- encode_list != encoded_list
if (sum(!encoded_list_worked) > 0) {
warning(sprintf("URI encoding failed for %s images", sum(!encoded_list_worked)))
}

tmp <- x[images]

# Replace only those pieces where the encoding actually worked
for(i in which(encoded_list_worked)) {
regmatches(tmp, gregexpr(encode_list[i], tmp, fixed = T)) <- encoded_list[i]
}

final[images] <- tmp
}
final
}
8 changes: 4 additions & 4 deletions man/moon_reader.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added tests/1x1.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions tests/testit/test-utils.R
Expand Up @@ -36,3 +36,22 @@ assert(
# A space is added in following (can't overwrite first space when 2nd char is *)
highlight_code(" * paste('a') #<<") %==% "* * paste('a')"
)

test_uri = "data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs="
assert(
"process_self_contained_images handles image specifications correctly",
process_self_contained_images("background-image: url(https://www.htmlgoodies.com/images/1x1.gif)") %==%
paste0("background-image: url(", test_uri, ")"),
process_self_contained_images("background-image: ../1x1.gif") %==%
paste0("background-image: ", test_uri),
process_self_contained_images("testing, testing <img src='https://www.htmlgoodies.com/images/1x1.gif'/>") %==%
paste0("testing, testing <img src='", test_uri, "'/>"),
process_self_contained_images("testing, testing <img src='https://www.htmlgoodies.com/images/1x1.gif'></img>") %==%
paste0("testing, testing <img src='", test_uri, "'></img>"),
process_self_contained_images("testing, testing `<img src='https://www.htmlgoodies.com/images/1x1.gif'/>`") %==%
"testing, testing `<img src='https://www.htmlgoodies.com/images/1x1.gif'/>`",
process_self_contained_images("testing, testing !()[https://www.htmlgoodies.com/images/1x1.gif]") %==%
paste0("testing, testing !()[https://www.htmlgoodies.com/images/1x1.gif]"),
process_self_contained_images("testing, testing `!()[https://www.htmlgoodies.com/images/1x1.gif]`") %==%
"testing, testing `!()[https://www.htmlgoodies.com/images/1x1.gif]`"
)