blob: c8b71854fd845901c69c33d6d16452aca3515fb0 [file] [log] [blame]
Hao Zhu73cf3732018-05-11 17:50:05 -04001#' Save kable to files
2#'
3#' @param x A piece of HTML code for tables, usually generated by kable and
4#' kableExtra
Hao Zhu7f8b6842018-10-23 17:41:13 -04005#' @param file save to files. If the input table is in HTML and the output file
6#' ends with `.png`, `.pdf` and `.jpeg`, `webshot` will be used to do the
Hao Zhu7039ecf2019-01-06 17:51:21 -05007#' conversion.
Hao Zhu73cf3732018-05-11 17:50:05 -04008#' @param bs_theme Which Bootstrap theme to use
9#' @param self_contained Will the files be self-contained?
Hao Zhu7039ecf2019-01-06 17:51:21 -050010#' @param extra_dependencies Additional HTML dependencies. For example,
11#' `list(`
12#' @param ... Additional variables being passed to `webshot::webshot`. This
13#' is for HTML only.
14#' @param latex_header_includes A character vector of extra LaTeX header stuff.
15#' Each element is a row. You can have things like
16#' `c("\\\\usepackage{threeparttable}", "\\\\usepackage{icons}")` You could
17#' probably add your language package here if you use non-English text in your
18#' table, such as `\\\\usepackage[magyar]{babel}`.
19#' @param keep_tex A T/F option to control if the latex file that is initially created
20#' should be kept. Default is `FALSE`.
Hao Zhuab670942020-08-19 15:35:35 -040021#' @param density density argument passed to magick if needed. Default is 300.
Jiaxiang Lie4b24f22019-04-05 13:38:11 +080022#' @examples
23#' \dontrun{
24#' library(kableExtra)
Hao Zhu73cf3732018-05-11 17:50:05 -040025#'
Jiaxiang Lie4b24f22019-04-05 13:38:11 +080026#' kable(mtcars[1:5, ], "html") %>%
27#' kable_styling("striped") %>%
28#' row_spec(1, color = "red") %>%
29#' save_kable("inst/test.pdf")
30#' }
Hao Zhu73cf3732018-05-11 17:50:05 -040031#' @export
32save_kable <- function(x, file,
Hao Zhu7039ecf2019-01-06 17:51:21 -050033 bs_theme = "simplex", self_contained = TRUE,
34 extra_dependencies = NULL, ...,
Hao Zhuab670942020-08-19 15:35:35 -040035 latex_header_includes = NULL, keep_tex = FALSE,
36 density = 300) {
Vincent Arel-Bundock75ecb622020-10-11 17:47:07 -040037
38 if (!is.null(attr(x, "format"))) {
39
40 # latex
41 if (attr(x, "format") == "latex") {
42 return(save_kable_latex(x, file, latex_header_includes, keep_tex, density))
43
Hao Zhu786698f2020-10-19 22:33:44 -040044 # markdown
Vincent Arel-Bundock75ecb622020-10-11 17:47:07 -040045 } else if (attr(x, "format") == "pipe") {
46
47 # good file extension: write to file
48 if (tools::file_ext(file) %in% c("txt", "md", "markdown", "Rmd")) {
49 return(save_kable_markdown(x, file))
50
Hao Zhu786698f2020-10-19 22:33:44 -040051 # bad file extension: warning + keep going to html writer
Vincent Arel-Bundock75ecb622020-10-11 17:47:07 -040052 } else {
53 warning('`save_kable` can only save markdown tables to files with the following extensions: .txt, .md, .markdown, .Rmd. Since the supplied file name has a different extension, `save_kable` will try to use the HTML writer. This is likely to produce suboptimal results. To save images or other file formats, try supplying a LaTeX or HTML table to `save_kable`.')
54 }
55
56 }
57
Hao Zhu7f8b6842018-10-23 17:41:13 -040058 }
Vincent Arel-Bundock75ecb622020-10-11 17:47:07 -040059
60 # html
Hao Zhu7039ecf2019-01-06 17:51:21 -050061 return(save_kable_html(x, file, bs_theme, self_contained,
Hao Zhuab670942020-08-19 15:35:35 -040062 extra_dependencies, density, ...))
Hao Zhu7f8b6842018-10-23 17:41:13 -040063}
64
Vincent Arel-Bundock75ecb622020-10-11 17:47:07 -040065
66save_kable_markdown <- function(x, file, ...) {
67 out <- paste(x, collapse="\n")
68 writeLines(text=out, con=file)
69 return(invisible(file))
70}
71
72
Hao Zhu7039ecf2019-01-06 17:51:21 -050073save_kable_html <- function(x, file, bs_theme, self_contained,
Hao Zhuab670942020-08-19 15:35:35 -040074 extra_dependencies, density, ...) {
Hao Zhu7039ecf2019-01-06 17:51:21 -050075 dependencies <- list(
Hao Zhu73cf3732018-05-11 17:50:05 -040076 rmarkdown::html_dependency_jquery(),
77 rmarkdown::html_dependency_bootstrap(theme = bs_theme),
Hao Zhuab670942020-08-19 15:35:35 -040078 html_dependency_lightable(),
Hao Zhu73cf3732018-05-11 17:50:05 -040079 html_dependency_kePrint()
80 )
Hao Zhu7039ecf2019-01-06 17:51:21 -050081 if (!is.null(extra_dependencies)) {
82 dependencies <- append(dependencies, extra_dependencies)
83 }
84
Hao Zhu46a42a12019-01-22 00:11:11 -050085 html_header <- htmltools::tags$head(dependencies)
Hao Zhu73cf3732018-05-11 17:50:05 -040086 html_table <- htmltools::HTML(as.character(x))
87 html_result <- htmltools::tagList(html_header, html_table)
Hao Zhu7f8b6842018-10-23 17:41:13 -040088
ghaarsma8e786ad2019-02-16 21:28:01 -060089
90 # Check if we are generating an image and use webshot to do that
Hao Zhu7f8b6842018-10-23 17:41:13 -040091 if (tools::file_ext(file) %in% c("png", "jpg", "jpeg", "pdf")) {
Hao Zhu454bb422020-10-19 16:36:36 -040092 file_temp_html <- tempfile(
93 pattern = tools::file_path_sans_ext(basename(file)),
94 fileext = ".html")
ghaarsma8e786ad2019-02-16 21:28:01 -060095 file.create(file_temp_html)
96 file_temp_html <- normalizePath(file_temp_html)
Hao Zhu454bb422020-10-19 16:36:36 -040097
ghaarsma8e786ad2019-02-16 21:28:01 -060098 file.create(file)
99 file <- normalizePath(file)
100
101 # Generate a random temp lib directory. The sub is to remove any back or forward slash at the beginning of the temp_dir
Hao Zhu454bb422020-10-19 16:36:36 -0400102 temp_dir <- sub(pattern = '^[\\\\/]{1,2}',
103 replacement = '',
104 tempfile(pattern = 'lib', tmpdir = '' , fileext = ''))
Hao Zhu786698f2020-10-19 22:33:44 -0400105 save_HTML(html_result, file = file_temp_html, libdir = temp_dir,
106 self_contained = FALSE)
ghaarsma8e786ad2019-02-16 21:28:01 -0600107
108 result <- webshot::webshot(file_temp_html, file, ...)
109 if (is.null(result)) {
110 # A webshot could not be created. Delete newly created files and issue msg
111 file.remove(file)
112 file.remove(file_temp_html)
113 message('save_kable could not create image with webshot package. Please check for any webshot messages')
Hao Zhuf1873a42019-01-07 15:57:01 -0500114 } else {
ghaarsma8e786ad2019-02-16 21:28:01 -0600115 if (tools::file_ext(file) == "pdf") {
116 message("Note that HTML color may not be displayed on PDF properly.")
117 }
118 # Remove temp html file and temp lib directory
119 file.remove(file_temp_html)
120 unlink(file.path(dirname(file_temp_html), temp_dir), recursive = TRUE)
121
122 if (requireNamespace("magick", quietly = TRUE)) {
Hao Zhuab670942020-08-19 15:35:35 -0400123 img_rework <- magick::image_read(file, density = density)
ghaarsma8e786ad2019-02-16 21:28:01 -0600124 img_rework <- magick::image_trim(img_rework)
125 img_info <- magick::image_info(img_rework)
Hao Zhuab670942020-08-19 15:35:35 -0400126 magick::image_write(img_rework, file, density = density)
ghaarsma8e786ad2019-02-16 21:28:01 -0600127 attr(file, "info") <- img_info
128 } else {
129 message("save_kable will have the best result with magick installed. ")
130 }
Hao Zhuf1873a42019-01-07 15:57:01 -0500131 }
ghaarsma8e786ad2019-02-16 21:28:01 -0600132
Hao Zhu7f8b6842018-10-23 17:41:13 -0400133 } else {
Hao Zhuf1873a42019-01-07 15:57:01 -0500134 file.create(file)
135 file <- normalizePath(file)
ghaarsma8e786ad2019-02-16 21:28:01 -0600136
Hao Zhu7f8b6842018-10-23 17:41:13 -0400137 if (self_contained) {
ghaarsma8e786ad2019-02-16 21:28:01 -0600138 # Generate a random temp lib directory. The sub is to remove any back or forward slash at the beginning of the temp_dir
Hao Zhu454bb422020-10-19 16:36:36 -0400139 temp_dir <- sub(pattern = '^[\\\\/]{1,2}',
140 replacement = '',
141 tempfile(pattern = 'lib', tmpdir = '' , fileext = ''))
Hao Zhu786698f2020-10-19 22:33:44 -0400142 save_HTML(html_result, file = file, libdir = temp_dir,
143 self_contained = TRUE)
ghaarsma8e786ad2019-02-16 21:28:01 -0600144 #remove_html_doc(file)
Hao Zhu786698f2020-10-19 22:33:44 -0400145 self_contained(file, file)
ghaarsma8e786ad2019-02-16 21:28:01 -0600146 unlink(file.path(dirname(file), temp_dir), recursive = TRUE)
147 } else {
Hao Zhu786698f2020-10-19 22:33:44 -0400148 # Simply use the htmltools::save_html to write out the files.
149 # Dependencies go to the standard lib folder
150 save_HTML(html_result, file = file, self_contained = FALSE)
Hao Zhu7f8b6842018-10-23 17:41:13 -0400151 }
Hao Zhu73cf3732018-05-11 17:50:05 -0400152 }
Hao Zhud8516932019-01-06 18:56:47 -0500153
Hao Zhuf1873a42019-01-07 15:57:01 -0500154 return(invisible(file))
Hao Zhu73cf3732018-05-11 17:50:05 -0400155}
Hao Zhu7f8b6842018-10-23 17:41:13 -0400156
Hao Zhu454bb422020-10-19 16:36:36 -0400157# Local version of htmltools::save_html with fix to relative path.
158# See https://github.com/rstudio/htmltools/pull/105
Hao Zhu786698f2020-10-19 22:33:44 -0400159save_HTML <- function(html, file, libdir = "lib", self_contained = TRUE) {
Hao Zhu454bb422020-10-19 16:36:36 -0400160 base_file <- basename(file)
161 dir <- dirname(file)
162 file <- file.path(dir, base_file)
163 oldwd <- setwd(dir)
164 on.exit(setwd(oldwd), add = TRUE)
165 rendered <- htmltools::renderTags(html)
166 deps <- lapply(rendered$dependencies, function(dep) {
167 dep <- htmltools::copyDependencyToDir(dep, libdir, FALSE)
168 dep <- htmltools::makeDependencyRelative(dep, dir, FALSE)
169 dep
170 })
Hao Zhu786698f2020-10-19 22:33:44 -0400171 html <- c(
172 if (self_contained) "" else "<!DOCTYPE html>",
173 "<html>", "<head>",
174 "<meta charset=\"utf-8\"/>",
175 "<title>table output</title>",
176 htmltools::renderDependencies(deps, c("href", "file")),
177 rendered$head, "</head>", "<body>",
178 rendered$html, "</body>", "</html>")
Hao Zhu454bb422020-10-19 16:36:36 -0400179 writeLines(html, file, useBytes = TRUE)
180}
181
Hao Zhu786698f2020-10-19 22:33:44 -0400182# Local version of rmarkdown::pandoc_self_contained_html(input, output) to
183# remove the no title bug
184self_contained <- function(input, output) {
185 input <- normalizePath(input)
186 if (!file.exists(output))
187 file.create(output)
188 output <- normalizePath(output)
189 template <- tempfile(fileext = ".html")
190 on.exit(unlink(template), add = TRUE)
Hao Zhuf6b60e82020-10-21 18:58:19 -0400191 write_utf8("$body$", template)
Hao Zhu786698f2020-10-19 22:33:44 -0400192 from <- if (rmarkdown::pandoc_available("1.17")) "markdown_strict" else "markdown"
193 rmarkdown::pandoc_convert(
194 input = input, from = from, output = output,
195 options = c("--metadata", 'pagetitle="table output"', "--self-contained",
196 "--template", template))
197 invisible(output)
198}
199
Hao Zhuf6b60e82020-10-21 18:58:19 -0400200# Local version of rmarkdown:::write_utf8
201write_utf8 <- function (text, con, ...) {
202 opts <- options(encoding = "native.enc")
203 on.exit(options(opts), add = TRUE)
204 writeLines(enc2utf8(text), con, ..., useBytes = TRUE)
205}
206
Hao Zhu786698f2020-10-19 22:33:44 -0400207
208
Hao Zhu46a42a12019-01-22 00:11:11 -0500209remove_html_doc <- function(x){
210 out <- paste(readLines(x)[-1], collapse = "\n")
211 writeLines(out, x)
212}
213
Hao Zhuab670942020-08-19 15:35:35 -0400214save_kable_latex <- function(x, file, latex_header_includes, keep_tex, density) {
Vincent Arel-Bundock97166b32020-08-26 11:42:23 -0400215
216 # if file extension is .tex, write to file, return the table as an
217 # invisible string, and do nothing else
218 if (tools::file_ext(file) == "tex") {
219 writeLines(x, file, useBytes = T)
220 return(invisible(x))
221 }
222
Hao Zhu7039ecf2019-01-06 17:51:21 -0500223 temp_tex <- c(
224 "\\documentclass[border=1mm, preview]{standalone}",
225 "\\usepackage[active,tightpage]{preview}",
226 "\\usepackage{varwidth}",
227 "\\usepackage{amssymb, amsmath}",
228 "\\usepackage{ifxetex,ifluatex}",
229 "\\usepackage{fixltx2e}",
230 "\\usepackage{polyglossia}",
Hao Zhu7039ecf2019-01-06 17:51:21 -0500231 latex_pkg_list(),
232 "\\usepackage{graphicx}",
Hao Zhu7039ecf2019-01-06 17:51:21 -0500233 "\\usepackage{xltxtra,xunicode}",
Hao Zhueac7b032020-08-19 14:07:03 -0400234 "\\usepackage{xcolor}",
Hao Zhu7039ecf2019-01-06 17:51:21 -0500235 latex_header_includes,
236 "\\begin{document}",
237 solve_enc(x),
238 "\\end{document}"
239 )
240 temp_tex <- paste(temp_tex, collapse = "\n")
Hao Zhu7f8b6842018-10-23 17:41:13 -0400241
Hao Zhud8516932019-01-06 18:56:47 -0500242 temp_tex_file <- paste0(tools::file_path_sans_ext(file), ".tex")
Hao Zhu7039ecf2019-01-06 17:51:21 -0500243 writeLines(temp_tex, temp_tex_file, useBytes = T)
Hao Zhuf1873a42019-01-07 15:57:01 -0500244 temp_tex_file <- normalizePath(temp_tex_file)
245 file_no_ext <- tools::file_path_sans_ext(temp_tex_file)
246
247 owd <- setwd(dirname(temp_tex_file))
248
AC Craft7cea5332020-04-19 16:01:07 -0400249 system(paste0('xelatex -interaction=batchmode "', temp_tex_file,'"'))
Hao Zhu7039ecf2019-01-06 17:51:21 -0500250 if (!keep_tex) {
Hao Zhuf1873a42019-01-07 15:57:01 -0500251 temp_file_delete <- paste0(file_no_ext, c(".tex", ".aux", ".log"))
Hao Zhu7039ecf2019-01-06 17:51:21 -0500252 unlink(temp_file_delete)
253 }
254
Hao Zhuf1873a42019-01-07 15:57:01 -0500255 table_img_info <- NULL
Hao Zhud8516932019-01-06 18:56:47 -0500256 if (tools::file_ext(file) != "pdf") {
257 table_img_pdf <- try(
Hao Zhuf1873a42019-01-07 15:57:01 -0500258 magick::image_read(paste0(file_no_ext, ".pdf"),
Hao Zhuab670942020-08-19 15:35:35 -0400259 density = density), silent = T)
Bill Evans30b84f52020-09-11 09:54:58 -0700260 if (inherits(table_img_pdf, "try-error")) {
Hao Zhud8516932019-01-06 18:56:47 -0500261 stop("We hit an error when trying to use magick to read the generated ",
Hao Zhu53e59612019-01-15 12:16:06 -0600262 "PDF file. You may check your magick installation and try to ",
263 "use magick::image_read to read the PDF file manually. It's also ",
264 "possible that you didn't have ghostscript installed.")
Hao Zhud8516932019-01-06 18:56:47 -0500265 }
Hao Zhuf1873a42019-01-07 15:57:01 -0500266 unlink(paste0(file_no_ext, ".pdf"))
Hao Zhud8516932019-01-06 18:56:47 -0500267 table_img <- magick::image_convert(table_img_pdf,
268 tools::file_ext(file))
Hao Zhuf1873a42019-01-07 15:57:01 -0500269 table_img_info <- magick::image_info(table_img)
270 magick::image_write(table_img,
Hao Zhuab670942020-08-19 15:35:35 -0400271 paste0(file_no_ext, ".", tools::file_ext(file)),
272 density = density)
Hao Zhu7039ecf2019-01-06 17:51:21 -0500273 }
Hao Zhu7039ecf2019-01-06 17:51:21 -0500274
Hao Zhuf1873a42019-01-07 15:57:01 -0500275 setwd(owd)
276
277 out <- paste0(file_no_ext, ".", tools::file_ext(file))
278 attr(out, "info") <- table_img_info
279 return(invisible(out))
Hao Zhu7f8b6842018-10-23 17:41:13 -0400280}