Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 1 | #' Save kable to files |
| 2 | #' |
| 3 | #' @param x A piece of HTML code for tables, usually generated by kable and |
| 4 | #' kableExtra |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 5 | #' @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 Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 7 | #' conversion. |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 8 | #' @param bs_theme Which Bootstrap theme to use |
| 9 | #' @param self_contained Will the files be self-contained? |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 10 | #' @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 Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 21 | #' @param density density argument passed to magick if needed. Default is 300. |
Jiaxiang Li | e4b24f2 | 2019-04-05 13:38:11 +0800 | [diff] [blame] | 22 | #' @examples |
| 23 | #' \dontrun{ |
| 24 | #' library(kableExtra) |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 25 | #' |
Jiaxiang Li | e4b24f2 | 2019-04-05 13:38:11 +0800 | [diff] [blame] | 26 | #' kable(mtcars[1:5, ], "html") %>% |
| 27 | #' kable_styling("striped") %>% |
| 28 | #' row_spec(1, color = "red") %>% |
| 29 | #' save_kable("inst/test.pdf") |
| 30 | #' } |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 31 | #' @export |
| 32 | save_kable <- function(x, file, |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 33 | bs_theme = "simplex", self_contained = TRUE, |
| 34 | extra_dependencies = NULL, ..., |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 35 | latex_header_includes = NULL, keep_tex = FALSE, |
| 36 | density = 300) { |
Vincent Arel-Bundock | 75ecb62 | 2020-10-11 17:47:07 -0400 | [diff] [blame] | 37 | |
| 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 | |
| 44 | # markdown |
| 45 | } 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 | |
| 51 | # bad file extension: warning + keep going to html writer |
| 52 | } 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 Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 58 | } |
Vincent Arel-Bundock | 75ecb62 | 2020-10-11 17:47:07 -0400 | [diff] [blame] | 59 | |
| 60 | # html |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 61 | return(save_kable_html(x, file, bs_theme, self_contained, |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 62 | extra_dependencies, density, ...)) |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 63 | } |
| 64 | |
Vincent Arel-Bundock | 75ecb62 | 2020-10-11 17:47:07 -0400 | [diff] [blame] | 65 | |
| 66 | save_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 Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 73 | save_kable_html <- function(x, file, bs_theme, self_contained, |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 74 | extra_dependencies, density, ...) { |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 75 | dependencies <- list( |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 76 | rmarkdown::html_dependency_jquery(), |
| 77 | rmarkdown::html_dependency_bootstrap(theme = bs_theme), |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 78 | html_dependency_lightable(), |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 79 | html_dependency_kePrint() |
| 80 | ) |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 81 | if (!is.null(extra_dependencies)) { |
| 82 | dependencies <- append(dependencies, extra_dependencies) |
| 83 | } |
| 84 | |
Hao Zhu | 46a42a1 | 2019-01-22 00:11:11 -0500 | [diff] [blame] | 85 | html_header <- htmltools::tags$head(dependencies) |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 86 | html_table <- htmltools::HTML(as.character(x)) |
| 87 | html_result <- htmltools::tagList(html_header, html_table) |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 88 | |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 89 | |
| 90 | # Check if we are generating an image and use webshot to do that |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 91 | if (tools::file_ext(file) %in% c("png", "jpg", "jpeg", "pdf")) { |
Hao Zhu | 454bb42 | 2020-10-19 16:36:36 -0400 | [diff] [blame^] | 92 | file_temp_html <- tempfile( |
| 93 | pattern = tools::file_path_sans_ext(basename(file)), |
| 94 | fileext = ".html") |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 95 | file.create(file_temp_html) |
| 96 | file_temp_html <- normalizePath(file_temp_html) |
Hao Zhu | 454bb42 | 2020-10-19 16:36:36 -0400 | [diff] [blame^] | 97 | |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 98 | 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 Zhu | 454bb42 | 2020-10-19 16:36:36 -0400 | [diff] [blame^] | 102 | temp_dir <- sub(pattern = '^[\\\\/]{1,2}', |
| 103 | replacement = '', |
| 104 | tempfile(pattern = 'lib', tmpdir = '' , fileext = '')) |
| 105 | save_HTML(html_result, file = file_temp_html, libdir = temp_dir) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 106 | |
| 107 | result <- webshot::webshot(file_temp_html, file, ...) |
| 108 | if (is.null(result)) { |
| 109 | # A webshot could not be created. Delete newly created files and issue msg |
| 110 | file.remove(file) |
| 111 | file.remove(file_temp_html) |
| 112 | message('save_kable could not create image with webshot package. Please check for any webshot messages') |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 113 | } else { |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 114 | if (tools::file_ext(file) == "pdf") { |
| 115 | message("Note that HTML color may not be displayed on PDF properly.") |
| 116 | } |
| 117 | # Remove temp html file and temp lib directory |
| 118 | file.remove(file_temp_html) |
| 119 | unlink(file.path(dirname(file_temp_html), temp_dir), recursive = TRUE) |
| 120 | |
| 121 | if (requireNamespace("magick", quietly = TRUE)) { |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 122 | img_rework <- magick::image_read(file, density = density) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 123 | img_rework <- magick::image_trim(img_rework) |
| 124 | img_info <- magick::image_info(img_rework) |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 125 | magick::image_write(img_rework, file, density = density) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 126 | attr(file, "info") <- img_info |
| 127 | } else { |
| 128 | message("save_kable will have the best result with magick installed. ") |
| 129 | } |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 130 | } |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 131 | |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 132 | } else { |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 133 | file.create(file) |
| 134 | file <- normalizePath(file) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 135 | |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 136 | if (self_contained) { |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 137 | # Generate a random temp lib directory. The sub is to remove any back or forward slash at the beginning of the temp_dir |
Hao Zhu | 454bb42 | 2020-10-19 16:36:36 -0400 | [diff] [blame^] | 138 | temp_dir <- sub(pattern = '^[\\\\/]{1,2}', |
| 139 | replacement = '', |
| 140 | tempfile(pattern = 'lib', tmpdir = '' , fileext = '')) |
| 141 | save_HTML(html_result, file = file, libdir = temp_dir) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 142 | #remove_html_doc(file) |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 143 | rmarkdown::pandoc_self_contained_html(file, file) |
ghaarsma | 8e786ad | 2019-02-16 21:28:01 -0600 | [diff] [blame] | 144 | unlink(file.path(dirname(file), temp_dir), recursive = TRUE) |
| 145 | } else { |
| 146 | # Simply use the htmltools::save_html to write out the files. Dependencies go to the standard lib folder |
| 147 | htmltools::save_html(html_result, file = file) |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 148 | } |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 149 | } |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 150 | |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 151 | return(invisible(file)) |
Hao Zhu | 73cf373 | 2018-05-11 17:50:05 -0400 | [diff] [blame] | 152 | } |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 153 | |
Hao Zhu | 454bb42 | 2020-10-19 16:36:36 -0400 | [diff] [blame^] | 154 | # Local version of htmltools::save_html with fix to relative path. |
| 155 | # See https://github.com/rstudio/htmltools/pull/105 |
| 156 | save_HTML <- function(html, file, background = "white", libdir="lib") { |
| 157 | base_file <- basename(file) |
| 158 | dir <- dirname(file) |
| 159 | file <- file.path(dir, base_file) |
| 160 | oldwd <- setwd(dir) |
| 161 | on.exit(setwd(oldwd), add = TRUE) |
| 162 | rendered <- htmltools::renderTags(html) |
| 163 | deps <- lapply(rendered$dependencies, function(dep) { |
| 164 | dep <- htmltools::copyDependencyToDir(dep, libdir, FALSE) |
| 165 | dep <- htmltools::makeDependencyRelative(dep, dir, FALSE) |
| 166 | dep |
| 167 | }) |
| 168 | html <- c("<!DOCTYPE html>", "<html>", "<head>", |
| 169 | "<meta charset=\"utf-8\" title=\"table output\"/>", |
| 170 | sprintf("<style>body{background-color:%s;}</style>", |
| 171 | htmltools::htmlEscape(background)), |
| 172 | htmltools::renderDependencies(deps, c("href", "file")), |
| 173 | rendered$head, "</head>", "<body>", |
| 174 | rendered$html, "</body>", "</html>") |
| 175 | writeLines(html, file, useBytes = TRUE) |
| 176 | } |
| 177 | |
Hao Zhu | 46a42a1 | 2019-01-22 00:11:11 -0500 | [diff] [blame] | 178 | remove_html_doc <- function(x){ |
| 179 | out <- paste(readLines(x)[-1], collapse = "\n") |
| 180 | writeLines(out, x) |
| 181 | } |
| 182 | |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 183 | save_kable_latex <- function(x, file, latex_header_includes, keep_tex, density) { |
Vincent Arel-Bundock | 97166b3 | 2020-08-26 11:42:23 -0400 | [diff] [blame] | 184 | |
| 185 | # if file extension is .tex, write to file, return the table as an |
| 186 | # invisible string, and do nothing else |
| 187 | if (tools::file_ext(file) == "tex") { |
| 188 | writeLines(x, file, useBytes = T) |
| 189 | return(invisible(x)) |
| 190 | } |
| 191 | |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 192 | temp_tex <- c( |
| 193 | "\\documentclass[border=1mm, preview]{standalone}", |
| 194 | "\\usepackage[active,tightpage]{preview}", |
| 195 | "\\usepackage{varwidth}", |
| 196 | "\\usepackage{amssymb, amsmath}", |
| 197 | "\\usepackage{ifxetex,ifluatex}", |
| 198 | "\\usepackage{fixltx2e}", |
| 199 | "\\usepackage{polyglossia}", |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 200 | latex_pkg_list(), |
| 201 | "\\usepackage{graphicx}", |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 202 | "\\usepackage{xltxtra,xunicode}", |
Hao Zhu | eac7b03 | 2020-08-19 14:07:03 -0400 | [diff] [blame] | 203 | "\\usepackage{xcolor}", |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 204 | latex_header_includes, |
| 205 | "\\begin{document}", |
| 206 | solve_enc(x), |
| 207 | "\\end{document}" |
| 208 | ) |
| 209 | temp_tex <- paste(temp_tex, collapse = "\n") |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 210 | |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 211 | temp_tex_file <- paste0(tools::file_path_sans_ext(file), ".tex") |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 212 | writeLines(temp_tex, temp_tex_file, useBytes = T) |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 213 | temp_tex_file <- normalizePath(temp_tex_file) |
| 214 | file_no_ext <- tools::file_path_sans_ext(temp_tex_file) |
| 215 | |
| 216 | owd <- setwd(dirname(temp_tex_file)) |
| 217 | |
AC Craft | 7cea533 | 2020-04-19 16:01:07 -0400 | [diff] [blame] | 218 | system(paste0('xelatex -interaction=batchmode "', temp_tex_file,'"')) |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 219 | if (!keep_tex) { |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 220 | temp_file_delete <- paste0(file_no_ext, c(".tex", ".aux", ".log")) |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 221 | unlink(temp_file_delete) |
| 222 | } |
| 223 | |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 224 | table_img_info <- NULL |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 225 | if (tools::file_ext(file) != "pdf") { |
| 226 | table_img_pdf <- try( |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 227 | magick::image_read(paste0(file_no_ext, ".pdf"), |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 228 | density = density), silent = T) |
Bill Evans | 30b84f5 | 2020-09-11 09:54:58 -0700 | [diff] [blame] | 229 | if (inherits(table_img_pdf, "try-error")) { |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 230 | stop("We hit an error when trying to use magick to read the generated ", |
Hao Zhu | 53e5961 | 2019-01-15 12:16:06 -0600 | [diff] [blame] | 231 | "PDF file. You may check your magick installation and try to ", |
| 232 | "use magick::image_read to read the PDF file manually. It's also ", |
| 233 | "possible that you didn't have ghostscript installed.") |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 234 | } |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 235 | unlink(paste0(file_no_ext, ".pdf")) |
Hao Zhu | d851693 | 2019-01-06 18:56:47 -0500 | [diff] [blame] | 236 | table_img <- magick::image_convert(table_img_pdf, |
| 237 | tools::file_ext(file)) |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 238 | table_img_info <- magick::image_info(table_img) |
| 239 | magick::image_write(table_img, |
Hao Zhu | ab67094 | 2020-08-19 15:35:35 -0400 | [diff] [blame] | 240 | paste0(file_no_ext, ".", tools::file_ext(file)), |
| 241 | density = density) |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 242 | } |
Hao Zhu | 7039ecf | 2019-01-06 17:51:21 -0500 | [diff] [blame] | 243 | |
Hao Zhu | f1873a4 | 2019-01-07 15:57:01 -0500 | [diff] [blame] | 244 | setwd(owd) |
| 245 | |
| 246 | out <- paste0(file_no_ext, ".", tools::file_ext(file)) |
| 247 | attr(out, "info") <- table_img_info |
| 248 | return(invisible(out)) |
Hao Zhu | 7f8b684 | 2018-10-23 17:41:13 -0400 | [diff] [blame] | 249 | } |