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

Isolate side effects in graphics functions #228

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
* Use code to generate the Unicode/LaTeX mapping table, to replace the previous
`R/sysdata.rda` solution. Now the mapping table is directly accessible
via `r2rtf:::unicode_latex` (thanks, @yihui, #218).
* Introduce "safe" versions of `graphics::par()` and `graphics::strwidth()`
that isolate their side effects by evaluating within a temporary BMP device.
This should avoid the generation of `Rplots.pdf` when when running r2rtf code
in certain context such as clicking the test buttons in RStudio environments
(#227).

# r2rtf 1.1.1

Expand Down
39 changes: 37 additions & 2 deletions R/rtf_strwidth.R
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ rtf_strwidth <- function(tbl) {
}

# Font size
text_cex <- attr(tbl, "text_font_size") / graphics::par("ps")
text_cex <- attr(tbl, "text_font_size") / safe_par("ps")

# Font format
# text format 1 (plain/normal), 2 (bold), 3 (italic), 4 (bold-italic)
Expand Down Expand Up @@ -107,7 +107,8 @@ rtf_strwidth <- function(tbl) {
grDevices::windowsFonts("Courier" = grDevices::windowsFont("Courier New"))
}

x$width <- graphics::strwidth(x$text,
x$width <- safe_strwidth(
x$text,
units = "inches",
cex = x$cex[1],
font = x$font[1],
Expand All @@ -131,3 +132,37 @@ rtf_strwidth <- function(tbl) {

width
}

#' Safe version of `graphics::par()` with side effects isolated
#'
#' @noRd
safe_par <- function(...) with_bmp(graphics::par(...))

#' Safe version of `graphics::strwidth()` with side effects isolated
#'
#' @noRd
safe_strwidth <- function(text, ...) with_bmp(graphics::strwidth(text, ...))

#' Evaluate expression within a BMP device and close it on exit
#'
#' See why: <https://github.com/Merck/r2rtf/issues/227>
#'
#' @noRd
with_bmp <- function(expr) {
expr <- substitute(expr)
grDevices::bmp(file_null())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my understanding. Why would bmp() device used instead of pdf()

on.exit(
{
current <- grDevices::dev.cur()
if (current > 1) {
prev <- grDevices::dev.prev(current)
grDevices::dev.off(current)
if (prev != current) grDevices::dev.set(prev)
}
},
add = TRUE
)
eval(expr, envir = parent.frame())
Comment on lines +152 to +165
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need substitute() + eval(), but can just take advantage of R's lazy evaluation of arguments, i.e., simply put expr at the end, and R will evaluate it at that time.

Suggested change
expr <- substitute(expr)
grDevices::bmp(file_null())
on.exit(
{
current <- grDevices::dev.cur()
if (current > 1) {
prev <- grDevices::dev.prev(current)
grDevices::dev.off(current)
if (prev != current) grDevices::dev.set(prev)
}
},
add = TRUE
)
eval(expr, envir = parent.frame())
grDevices::bmp(file_null())
on.exit(
{
current <- grDevices::dev.cur()
if (current > 1) {
prev <- grDevices::dev.prev(current)
grDevices::dev.off(current)
if (prev != current) grDevices::dev.set(prev)
}
},
add = TRUE
)
expr

}

file_null <- function() if (.Platform$OS.type == "windows") "nul:" else "/dev/null"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use "tempfile" to avoid a OS dependent logic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess writing to the null device is often faster than a real file.

9 changes: 9 additions & 0 deletions tests/testthat/test-developer-testing-safe-graphics.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
test_that("Running graphics functions within with_bmp() does not affect graphics device", {
dev_cur <- grDevices::dev.cur()

with_bmp(par("mar"))
safe_par("font")
safe_strwidth("Hello world!", units = "inches", cex = 1, font = 1, family = "sans")

expect_equal(grDevices::dev.cur(), dev_cur)
})
4 changes: 2 additions & 2 deletions tests/testthat/test-independent-testing-rtf_nrow.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ cellsize <- matrix(width, nrow = nrow(tbl), ncol = ncol(tbl), byrow = TRUE) - pa
nline_vector <- rtf_nline_vector(
text = c("title 1", "this is a sentence for title 2", NA),
strwidth = c(
strwidth("title 1", units = "inches"),
strwidth("this is a sentence for title 2", units = "inches")
safe_strwidth("title 1", units = "inches"),
safe_strwidth("this is a sentence for title 2", units = "inches")
),
size = 0.5
)
Expand Down
6 changes: 4 additions & 2 deletions tests/testthat/test-independent-testing-rtf_strwidth.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ tbl <- df |>

strw <- round(r2rtf:::rtf_strwidth(tbl), 5)

size8italic <- round(graphics::strwidth("This is a long sentence",
size8italic <- round(safe_strwidth(
"This is a long sentence",
units = "inches",
cex = 2 / 3,
font = 3,
family = "Times"
), 5)

bold <- round(graphics::strwidth("third",
bold <- round(safe_strwidth(
"third",
units = "inches",
cex = 2 / 3,
font = 2,
Expand Down
Loading