Skip to content

Commit 03a70be

Browse files
committed
feat: add base64_to_image(), as_base64(), from_base64() and plot_in_png()
1 parent cc03584 commit 03a70be

File tree

11 files changed

+538
-42
lines changed

11 files changed

+538
-42
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: officer
33
Title: Manipulation of Microsoft Word and PowerPoint Documents
4-
Version: 0.7.2.003
4+
Version: 0.7.2.004
55
Authors@R: c(
66
person("David", "Gohel", , "david.gohel@ardata.fr", role = c("aut", "cre")),
77
person("Stefan", "Moog", , "moogs@gmx.de", role = "aut"),

NAMESPACE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ export("slide_visible<-")
154154
export(add_sheet)
155155
export(add_slide)
156156
export(annotate_base)
157+
export(as_base64)
158+
export(base64_to_image)
157159
export(block_caption)
158160
export(block_gg)
159161
export(block_list)
@@ -230,6 +232,7 @@ export(fp_tabs)
230232
export(fp_text)
231233
export(fp_text_lite)
232234
export(fpar)
235+
export(from_base64)
233236
export(ftext)
234237
export(get_reference_value)
235238
export(headers_replace_all_text)
@@ -280,6 +283,7 @@ export(ph_with.plot_instr)
280283
export(ph_with.unordered_list)
281284
export(ph_with.xml_document)
282285
export(phs_with)
286+
export(plot_in_png)
283287
export(plot_instr)
284288
export(plot_layout_properties)
285289
export(pptx_summary)
@@ -364,6 +368,7 @@ importFrom(graphics,box)
364368
importFrom(graphics,plot)
365369
importFrom(graphics,rect)
366370
importFrom(graphics,text)
371+
importFrom(openssl,base64_decode)
367372
importFrom(openssl,base64_encode)
368373
importFrom(openssl,sha1)
369374
importFrom(ragg,agg_png)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
- fix an issue with bookmark selections in `docx_summary()`.
77
- fix `fp_par_lite()` management of argument `tabs`.
88

9+
## Features
10+
11+
- new internal utilities `base64_to_image()`, `as_base64()`, `from_base64()`
12+
and `plot_in_png()`.
13+
914

1015
# officer 0.7.1
1116

R/dev-utils.R

Lines changed: 229 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,238 @@
1+
#' @title Write a ggplot Object to PNG File
2+
#'
3+
#' @description Renders a ggplot object to a PNG file using ragg for high-quality output.
4+
#'
5+
#' @param ggobj A ggplot object to render
6+
#' @param width Numeric, width of the output image
7+
#' @param height Numeric, height of the output image
8+
#' @param res Numeric, resolution in DPI (default 200)
9+
#' @param units Character, units for width and height ("in", "cm", "mm", "px") (default "in")
10+
#' @param pointsize Integer, The default pointsize of the device in pt
11+
#' @param scaling scaling factor to apply
12+
#' @param path Character, output file path. If NULL, a temporary file is created (default NULL)
13+
#'
14+
#' @return Character, the path to the created PNG file
15+
#'
16+
#' @importFrom ragg agg_png
17+
#' @importFrom grDevices dev.off
18+
#' @examples
19+
#' plot_in_png(
20+
#' code = {
21+
#' barplot(1:10)
22+
#' },
23+
#' width = 5,
24+
#' height = 4,
25+
#' res = 72,
26+
#' units = "in"
27+
#' )
28+
#' @export
29+
#' @keywords internal
30+
plot_in_png <- function(
31+
ggobj = NULL,
32+
code = NULL,
33+
width,
34+
height,
35+
res = 200,
36+
units = "in",
37+
pointsize = 11,
38+
scaling = 1,
39+
path = NULL
40+
) {
41+
if (is.null(path)) {
42+
path <- tempfile(fileext = ".png")
43+
}
44+
45+
if (!is.null(ggobj)) {
46+
code <- str2lang("print(ggobj)")
47+
}
48+
49+
agg_png(
50+
filename = path,
51+
width = width,
52+
height = height,
53+
units = units,
54+
background = "transparent",
55+
res = res,
56+
pointsize = pointsize,
57+
scaling = scaling
58+
)
59+
tryCatch(
60+
{
61+
eval(code)
62+
},
63+
finally = dev.off()
64+
)
65+
path
66+
}
67+
68+
#' @importFrom openssl base64_encode
69+
#' @export
70+
#' @title Encode Character Vector to Base64
71+
#' @description
72+
#' Encodes one or more elements of a character vector into Base64 format.
73+
#' @param x A character vector. NA values are preserved.
74+
#' @return A character vector of Base64-encoded strings, same length as `x`.
75+
#' @examples
76+
#' as_base64(letters)
77+
#' as_base64(c("hello", NA, "world"))
78+
#' @keywords internal
79+
as_base64 <- function(x) {
80+
if (!is.character(x)) {
81+
stop("'x' must be a character vector.")
82+
}
83+
z <- vapply(
84+
x,
85+
function(elem) {
86+
if (is.na(elem)) {
87+
NA_character_
88+
} else {
89+
base64_encode(charToRaw(elem))
90+
}
91+
},
92+
NA_character_
93+
)
94+
unname(z)
95+
}
96+
97+
#' @importFrom openssl base64_decode
98+
#' @export
99+
#' @title Decode Base64 Vector to Character
100+
#' @description
101+
#' Decodes one or more Base64-encoded elements back into their original character form.
102+
#' @param x A character vector of Base64-encoded strings. NA values are preserved.
103+
#' @return A character vector of decoded strings, same length as `x`.
104+
#' @examples
105+
#' z <- as_base64(c("hello", "world"))
106+
#' from_base64(z)
107+
#' @keywords internal
108+
from_base64 <- function(x) {
109+
if (!is.character(x)) {
110+
stop("'x' must be a character vector of Base64 strings.")
111+
}
112+
z <- vapply(
113+
x,
114+
function(elem) {
115+
if (is.na(elem)) {
116+
NA_character_
117+
} else {
118+
raw <- tryCatch(
119+
{
120+
b64_str <- base64_decode(elem)
121+
if(all(nchar(b64_str) < 1)) {
122+
stop("empty result")
123+
}
124+
b64_str
125+
},
126+
error = function(e) {
127+
stop("Failed to decode Base64 element: '", elem, "'.", call. = FALSE)
128+
}
129+
)
130+
rawToChar(raw)
131+
}
132+
},
133+
NA_character_
134+
)
135+
unname(z)
136+
}
137+
138+
139+
#' Convert Data URIs to PNG Files
140+
#'
141+
#' @description Decodes base64-encoded data URIs and writes them to PNG files.
142+
#'
143+
#' @param data_uri Character, a data URI character vector starting with "data:image/png;base64,"
144+
#' @param output_files Character, paths to the output PNG files
145+
#'
146+
#' @return Character, the paths to the created PNG files
147+
#'
148+
#' @importFrom openssl base64_decode
149+
#' @examples
150+
#' rlogo <- file.path(R.home("doc"), "html", "logo.jpg")
151+
#' base64_str <- image_to_base64(rlogo)
152+
#' base64_to_image(
153+
#' data_uri = base64_str,
154+
#' output_files = tempfile(fileext = ".jpeg")
155+
#' )
156+
#' @export
157+
base64_to_image <- function(data_uri, output_files) {
158+
159+
for(i in seq_along(data_uri)) {
160+
base64_part <- sub("^data:image/[^;]+;base64,", "", data_uri[[i]])
161+
raw_data <- base64_decode(base64_part)
162+
writeBin(raw_data, output_files[[i]])
163+
}
164+
165+
output_files
166+
}
167+
168+
mime_type <- function(paths) {
169+
result <- character(length(paths))
170+
pattern <- "\\.(png|gif|jpg|jpeg|svg|tiff|pdf|webp)$"
171+
m <- regexpr(pattern = pattern, text = paths)
172+
result[attr(m, "match.length") > -1] <- regmatches(paths, m)
173+
result <- gsub("\\.jpg", ".jpeg", result)
174+
result <- gsub("\\.svg", ".svg+xml", result)
175+
result <- gsub("^\\.{1}", "", result)
176+
prefix <- ifelse(result %in% "pdf", "application", "image")
177+
result <- paste(prefix, result, sep = "/")
178+
result[attr(m, "match.length") < 0] <- NA_character_
179+
result
180+
}
181+
1182
#' @importFrom openssl base64_encode
2183
#' @export
3184
#' @title Images to base64
4185
#' @description encodes images into base64 strings.
5186
#' @param filepaths file names.
6187
#' @keywords internal
7188
#' @examples
8-
#' rlogo <- file.path( R.home("doc"), "html", "logo.jpg")
9-
#' image_to_base64(rlogo)
10-
image_to_base64 <- function(filepaths){
11-
vapply(
12-
filepaths,
13-
function(filepath){
14-
if( grepl("\\.png", ignore.case = TRUE, x = filepath) ){
15-
mime <- "image/png"
16-
} else if( grepl("\\.gif", ignore.case = TRUE, x = filepath) ){
17-
mime <- "image/gif"
18-
} else if( grepl("\\.jpg", ignore.case = TRUE, x = filepath) ){
19-
mime <- "image/jpeg"
20-
} else if( grepl("\\.jpeg", ignore.case = TRUE, x = filepath) ){
21-
mime <- "image/jpeg"
22-
} else if( grepl("\\.svg", ignore.case = TRUE, x = filepath) ){
23-
mime <- "image/svg+xml"
24-
} else if( grepl("\\.tiff", ignore.case = TRUE, x = filepath) ){
25-
mime <- "image/tiff"
26-
} else if( grepl("\\.pdf", ignore.case = TRUE, x = filepath) ){
27-
mime <- "application/pdf"
28-
} else if( grepl("\\.webp", ignore.case = TRUE, x = filepath) ){
29-
mime <- "image/webp"
30-
} else {
31-
stop(sprintf("'officer' does not know how to encode format of the file '%s'.", filepath))
32-
}
33-
if(!file.exists(filepath)){
34-
stop(sprintf("file '%s' can not be found.",filepath))
35-
}
36-
dat <- readBin(filepath, what = "raw", size = 1, endian = "little", n = 1e+8)
189+
#' rlogo <- file.path(R.home("doc"), "html", "logo.jpg")
190+
#' base64_str <- image_to_base64(rlogo)
191+
image_to_base64 <- function(filepaths) {
192+
193+
mimes <- mime_type(paths = filepaths)
194+
if (any(is.na(mimes))) {
195+
cli::cli_abort(
196+
paste0(
197+
"Unknown image(s) format: ",
198+
cli::ansi_collapse(
199+
basename(filepaths)[is.na(mimes)],
200+
trunc = 5
201+
)
202+
)
203+
)
204+
}
205+
206+
if (any(!file.exists(filepaths))) {
207+
cli::cli_abort(
208+
paste0(
209+
"File(s) not found: ",
210+
cli::ansi_collapse(
211+
basename(filepaths)[!file.exists(filepaths)],
212+
trunc = 5
213+
)
214+
)
215+
)
216+
}
217+
218+
base64_lst <- mapply(
219+
FUN = function(filepath, mime) {
220+
dat <- readBin(
221+
filepath,
222+
what = "raw",
223+
size = 1,
224+
endian = "little",
225+
n = file.info(filepath)$size
226+
)
37227
base64_str <- base64_encode(bin = dat)
38228
base64_str <- sprintf("data:%s;base64,%s", mime, base64_str)
39229
base64_str
40230
},
41-
FUN.VALUE = "")
231+
filepath = filepaths,
232+
mime = mimes,
233+
SIMPLIFY = FALSE
234+
)
235+
unname(unlist(base64_lst))
42236
}
43237

44238
#' @importFrom uuid UUIDgenerate
@@ -56,11 +250,11 @@ uuid_generate <- function(n = 1, ...) {
56250
}
57251

58252
.url_special_chars <- list(
59-
`&` = '&amp;',
60-
`<` = '&lt;',
61-
`>` = '&gt;',
62-
`'` = '&#39;',
63-
`"` = '&quot;',
253+
`&` = "&amp;",
254+
`<` = "&lt;",
255+
`>` = "&gt;",
256+
`'` = "&#39;",
257+
`"` = "&quot;",
64258
` ` = "&nbsp;"
65259
)
66260

R/utils.R

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,6 @@ between <- function(x, left, right ){
170170

171171

172172

173-
simple_lag <- function( x, default=0 ){
174-
c(default, x[-length(x)])
175-
}
176-
177173
rbind_match_columns <- function(list_df) {
178174

179175
col <- unique(unlist(lapply(list_df, colnames)))

man/as_base64.Rd

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)