#' Add Extra CSS Styles #' #' Adds css elements to target `outfile`, typically a xaringanthemer css file. #' The `css` argument takes a list of CSS classes and definitions (see examples below) #' and appends CSS rules to `outfile`. #' #' @section css list: #' The `css` input must be a named list of css properties and values within a #' named list of class identifiers, for example #' `list(".class-id" = list("css-property" = "value"))`. #' #' @param css A named list of CSS definitions each containing a named list #' of CSS property-value pairs, i.e. #' `list(".class-id" = list("css-property" = "value"))` #' @param append If `TRUE` output will be appended to `outfile`; otherwise, #' it will overwrite the contents of `outfile`. #' @param heading Heading added above extra CSS. Use `NULL` to disable. #' #' @examples #' style_extra_css( #' outfile = stdout(), #' css = list( #' ".red" = list(color = "red"), #' ".small" = list("font-size" = "90%"), #' ".full-width" = list( #' display = "flex", #' width = "100%", #' flex = "1 1 auto" #' ) #' ) #' ) #' @inheritParams style_xaringan #' @export style_extra_css <- function( css, outfile = "xaringan-themer.css", append = TRUE, heading = "Extra CSS" ) { has_heading <- !is.null(heading) x <- paste0( if (has_heading) paste0("/* ", heading, " */\n"), list2css(css) ) if (append) x <- paste0(if (has_heading) "\n\n" else "\n", x) if (is.null(outfile)) return(x) cat( x, file = outfile, append = append, sep = "\n" ) invisible(x) } #' @inheritParams style_extra_css #' @keywords internal list2css <- function(css) { `%.%` <- function(x, y) paste0(x, y) error <- NULL if (is.null(names(css))) { stop("All elements in `css` list must be named", call. = FALSE) } if (purrr::vec_depth(css) != 3) { stop( "`css` list must be a named list within a named list, e.g.:\n", ' list(".class-id" = list("css-property" = "value"))' ) } if (any(names(css) == "")) { not_named <- which(names(css) == "") if (length(not_named) > 1) { stop( call. = FALSE, "All elements in `css` list must be named. Items ", paste(not_named, collapse = ", "), " are unnamed." ) } else { stop( call. = FALSE, "All elements in `css` list must be named. Item ", not_named, " is not named." ) } } child_unnamed <- purrr::map_lgl(purrr::map(css, ~ { is.null(names(.)) || any(names(.) == "") }), ~ any(.)) if (any(child_unnamed)) { has_unnamed <- names(css)[child_unnamed] msg <- paste( "All properties of elements in `css` list must be named.", if (length(has_unnamed) > 1) "Elements" else "Element", paste(has_unnamed, collapse = ", "), if (length(has_unnamed) > 1) "have" else "has", "unnamed property or properties." ) stop(msg, call. = FALSE) } purrr::map_chr(names(css), function(el) { paste( sep = "\n", el %.% " {", paste( purrr::map_chr(names(css[[el]]), function(prop) { " " %.% prop %.% ": " %.% css[[el]][[prop]] %.% ";" }), collapse = "\n" ), "}" ) }) } list2fonts <- function(fonts) { if ( length(setdiff(names(google_font('fam')), names(fonts))) == 0 && !inherits(fonts, "google_font") ) { # concatenating a string and a google_font() provides a wacky list stop( "Multiple fonts in `extra_fonts` must be specified inside a `list()`.", call. = FALSE ) } if (inherits(fonts, "google_font")) { fonts <- list(fonts) } fonts <- purrr::map_chr(fonts, function(f) { if (inherits(f, "google_font")) { f$url } else if (inherits(f, "character")) { f } else { NA_character_ } }) paste0("@import url(", fonts[!is.na(fonts)], ");") }