support "any" graphic device

- shift temp-file generation from time-based to 'tempfile()', reduces
  the (slim) possibility of same-name images being created (e.g.,
  parallel production, fast rendering)
- add some graphic helper funcs for generalizing things across all
  'spec_*' plotting functions: is_svg, dev_chr, and graphics_dev
- add 'make_inline_plot' to centralize formation of the
  'kableExtraInlinePlots' object; prepend 'file:///' to paths when not
  is_latex, fixes display of some raster graphics; added "list" as a
  secondary class
- add 'listify_args' to enable any single-plot function to be able to
  vectorize any of its arguments (so that different plots on different
  rows can have different arguments)
- update 'spec_plot' to use these mechanisms
diff --git a/R/mini_plots.R b/R/mini_plots.R
index a1463f9..f2cbb6f 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -258,8 +258,9 @@
 #' default to the value of `minmax`. Set to an empty `list()` to disable.
 #' @param dir Directory of where the images will be saved.
 #' @param file File name. If not provided, a random name will be used
-#' @param file_type Graphic device. Support `png` or `svg`. SVG is recommended
-#' for HTML output.
+#' @param file_type Graphic device. Can be character (e.g., `"pdf"`)
+#'   or a graphics device function (`grDevices::pdf`). This defaults
+#'   to `"pdf"` if the rendering is in LaTeX and `"svg"` otherwise.
 #' @param ... extra parameters passing to `plot`
 #'
 #' @export
@@ -272,7 +273,7 @@
                       minmax = list(pch = ".", cex = cex, col = "red"),
                       min = minmax, max = minmax,
                       dir = if (is_latex()) rmd_files_dir() else tempdir(),
-                      file = NULL, file_type = if (is_latex()) "png" else "svg", ...) {
+                      file = NULL, file_type = if (is_latex()) "pdf" else "svg", ...) {
   if (is.list(x)) {
     lenx <- length(x)
 
@@ -291,53 +292,11 @@
       stop("'x' and 'y' are not the same length")
     }
 
-    # any of the arguments can be per-plot controlling, but an arg
-    # that is normally not length-1 may be recycled (incorrectly) by
-    # Map, so we have to listify them if not already lists;
-    # enforce a restriction of recycling only length 1 or lenx
-
-    # first, start with the literals (x,y)
-    # (same_lim is not a factor anymore)
-    dots <- list(x = x, y = y)
-
-    # second, we know these args are likely to be vectors (length > 1)
-    # or lists, so we have to handle them carefully and double-list if
-    # present
-    notlen1vec <- list(xlim = xlim, ylim = ylim)
-    dots <- c(dots, Map(
-      function(L, nm) {
-        if (is.null(L)) return(list(NULL))
-        if (!is.list(L)) return(list(L))
-        if (length(L) == lenx) return(L)
-        stop("length of '", nm, "' must be 1 or the same length as 'x'")
-      }, notlen1vec, names(notlen1vec)))
-
-    # these are special ... they are lists which may need to be
-    # nested, and we can't pass NULL since that may default to the
-    # actual values instead of the intended
-    notlen1lst <- list(minmax = minmax, min = min, max = max)
-    dots <- c(dots, Map(
-      function(L, nm) {
-        if (is.null(L)) return(list(NULL))
-        if (!length(L)) return(list(list()))
-        if (!is.list(L[[1]])) return (list(L))
-        if (length(L) == lenx) return(L)
-        stop("length of '", nm, "' must be 1 or the same length as 'x'")
-      }, notlen1lst, names(notlen1lst)))
-
-    # last, all remaining arguments that we don't already know about
-    # are length-1, so can be easily listified; using 'formals()'
-    # allows us to not hard-code all params
-    len1args <- mget(setdiff(names(formals()),
-                             c(names(dots), "same_lim", "x", "y", "...")))
-    dots <- c(dots, Map(
-      function(V, nm) {
-        if (is.null(V)) return(list(NULL))
-        if (!length(V) %in% c(1L, lenx)) {
-          stop("length of '", nm, "' must be 1 or the same length as 'x'")
-        }
-        V
-      }, len1args, names(len1args)))
+    dots <- listify_args(x, y = y, width, height, res,
+                         xlim, ylim, xaxt, yaxt, ann, col, border, frame.plot,
+                         lwd, pch, cex, type, polymin, minmax, min, max,
+                         dir, file, file_type,
+                         lengths = c(1, lenx))
 
     return(do.call(Map, c(list(f = spec_plot), dots)))
 
@@ -371,24 +330,20 @@
   xlim <- xlim + diff(xlim) * expand
   ylim <- ylim + diff(ylim) * expand
 
-  file_type <- match.arg(file_type, c("svg", "png"))
-
   if (!dir.exists(dir)) {
     dir.create(dir)
   }
 
+  file_ext <- dev_chr(file_type)
   if (is.null(file)) {
-    file <- file.path(dir, paste0(
-      "hist_", round(as.numeric(Sys.time()) * 1000), ".", file_type))
+    file <- normalizePath(
+      tempfile(pattern = "plot_", tmpdir = dir, fileext = paste0(".", file_ext)),
+      winslash = "/", mustWork = FALSE)
   }
 
-  if (file_type == "svg") {
-    grDevices::svg(filename = file, width = width / res, height = height / res,
-                   bg = 'transparent')
-  } else {
-    grDevices::png(filename = file, width = width, height = height, res = res,
-                   bg = 'transparent')
-  }
+  graphics_dev(filename = file, dev = file_type,
+               width = width, height = height, res = res,
+               bg = "transparent")
   curdev <- grDevices::dev.cur()
   on.exit(grDevices::dev.off(curdev), add = TRUE)
 
@@ -428,17 +383,9 @@
 
   grDevices::dev.off(curdev)
 
-  if (file_type == "svg") {
-    svg_xml <- xml2::read_xml(file)
-    svg_text <- as.character(svg_xml)
-    unlink(file)
-  } else {
-    svg_text <- NULL
-  }
-  out <- list(path = file, dev = file_type, type = "line",
-              width = width, height = height, res = res,
-              svg_text = svg_text)
-
-  class(out) <- "kableExtraInlinePlots"
+  out <- make_inline_plot(
+    file, file_ext, file_type,
+    width, height, res,
+    del = TRUE)
   return(out)
 }