#' Apply the Civilytics brand styling to a flextable #' #' Applies *only* the visual Civilytics brand to an already-structured #' [flextable::flextable()] — header fill and color, body font and size, #' zebra striping, borders, footer styling, and a fixed table layout. The #' caller remains responsible for table *structure*: labels #' ([flextable::set_header_labels()]), header/title lines #' ([flextable::add_header_lines()]), column widths ([flextable::width()]), #' alignment ([flextable::align()]), and footer text #' ([flextable::add_footer_lines()]). This separation keeps styling reusable #' across projects while leaving content decisions where they belong. #' #' `flextable`, `officer`, and `ragg` are Suggested (not Imported) to keep the #' base install light, so this function errors with an install hint if #' `flextable` is unavailable. #' #' @param ft A [flextable::flextable()] object. #' @param header_bg Character. Header background fill. Default `"#2c3e50"`. #' @param header_color Character. Header text color. Default `"white"`. #' @param title_fontsize Numeric. Font size for the first header line (the #' title row, `i = 1`). Default `14`. #' @param body_fontsize Numeric. Body font size. Default `11`. #' @param font_name Character. Font family applied to all parts. Default #' `"Arial"`. #' @param zebra Logical. Apply alternating-row striping to even body rows. #' Default `TRUE`. Safely skipped for tables with fewer than two body rows. #' @param zebra_bg Character. Fill color for striped (even) body rows. #' Default `"#f0f0eb"`. #' @param outer_border_color Character. Outer border color. Default #' `"#888888"`. #' @param outer_border_width Numeric. Outer border width. Default `1`. #' @param inner_border_color Character. Inner horizontal border color (body). #' Default `"#cccccc"`. #' @param inner_border_width Numeric. Inner horizontal border width. Default #' `0.5`. #' @param footer_fontsize Numeric. Footer font size (applied only if a footer #' part exists). Default `9`. #' @param footer_color Character. Footer text color. Default `"#555555"`. #' #' @return The styled [flextable::flextable()] object. #' @export #' @seealso [save_branded_flextable_png()] to export the styled table to a #' logo-stamped PNG. #' @examples #' \dontrun{ #' library(flextable) #' ft <- flextable(head(mtcars)) |> #' add_header_lines("Motor Trend Cars") |> #' add_footer_lines("Source: mtcars") |> #' style_flextable_civilytics() #' } style_flextable_civilytics <- function(ft, header_bg = "#2c3e50", header_color = "white", title_fontsize = 14, body_fontsize = 11, font_name = "Arial", zebra = TRUE, zebra_bg = "#f0f0eb", outer_border_color = "#888888", outer_border_width = 1, inner_border_color = "#cccccc", inner_border_width = 0.5, footer_fontsize = 9, footer_color = "#555555") { if (!requireNamespace("flextable", quietly = TRUE)) { stop("Install 'flextable' to use this function.", call. = FALSE) } if (!requireNamespace("officer", quietly = TRUE)) { stop("Install 'officer' to use this function.", call. = FALSE) } # Header: navy fill, white bold text, larger title line. ft <- flextable::bg(ft, bg = header_bg, part = "header") ft <- flextable::color(ft, color = header_color, part = "header") ft <- flextable::bold(ft, part = "header") ft <- flextable::fontsize(ft, i = 1, size = title_fontsize, part = "header") # Body: readable size, brand font across all parts. ft <- flextable::fontsize(ft, size = body_fontsize, part = "body") ft <- flextable::font(ft, fontname = font_name, part = "all") # Zebra striping on even body rows, guarded for tables with < 2 rows. nrow_body <- flextable::nrow_part(ft, part = "body") if (zebra && nrow_body >= 2) { ft <- flextable::bg(ft, i = seq(2, nrow_body, 2), bg = zebra_bg, part = "body") } # Borders: outer frame on all parts, light horizontal rules in the body. ft <- flextable::border_outer( ft, border = officer::fp_border(color = outer_border_color, width = outer_border_width), part = "all" ) ft <- flextable::border_inner_h( ft, border = officer::fp_border(color = inner_border_color, width = inner_border_width), part = "body" ) # Footer styling — only if the table actually has a footer part. if (flextable::nrow_part(ft, part = "footer") > 0) { ft <- flextable::fontsize(ft, size = footer_fontsize, part = "footer") ft <- flextable::color(ft, color = footer_color, part = "footer") } flextable::set_table_properties(ft, layout = "fixed") } #' Save a branded flextable to a logo-stamped PNG #' #' Exports a styled [flextable::flextable()] to a PNG via #' [ragg::agg_png()] (sized to the table's own dimensions plus a little #' headroom for the logo), then optionally stamps the Civilytics logo onto the #' file with [stamp_logo_png()] so file-based tables stay visually consistent #' with [civilytics_logo()]-branded plots. #' #' `ragg` and `flextable` are Suggested (not Imported); this function errors #' with an install hint if either is unavailable. #' #' @param ft A [flextable::flextable()] object, typically already styled with #' [style_flextable_civilytics()]. #' @param path Character. Output PNG path. Returned invisibly. #' @param logo Logical. Stamp the Civilytics logo onto the saved PNG via #' [stamp_logo_png()]. Default `TRUE`. #' @param res Numeric. Output resolution in PPI passed to [ragg::agg_png()]. #' Default `300`. #' @param extra_height Numeric. Additional height in inches added to the #' table's natural height to leave room for the stamped logo. Default `0.4`. #' @param ... Additional arguments forwarded to [stamp_logo_png()] (e.g. #' `type`, `variant`, `position`, `width_frac`, `margin_frac`). #' #' @return `path`, invisibly. #' @export #' @seealso [style_flextable_civilytics()] to apply the brand styling, and #' [stamp_logo_png()] for the underlying logo compositing. #' @examples #' \dontrun{ #' library(flextable) #' ft <- flextable(head(mtcars)) |> #' add_footer_lines("Source: mtcars") |> #' style_flextable_civilytics() #' save_branded_flextable_png(ft, "table.png") #' save_branded_flextable_png(ft, "table.png", position = "bottom-left") #' knitr::include_graphics("table.png") #' } save_branded_flextable_png <- function(ft, path, logo = TRUE, res = 300, extra_height = 0.4, ...) { if (!requireNamespace("flextable", quietly = TRUE)) { stop("Install 'flextable' to use this function.", call. = FALSE) } if (!requireNamespace("ragg", quietly = TRUE)) { stop("Install 'ragg' to use this function.", call. = FALSE) } d <- flextable::flextable_dim(ft) ragg::agg_png(path, width = d$width, height = d$height + extra_height, units = "in", res = res) on.exit(grDevices::dev.off(), add = TRUE) plot(ft) if (logo) { # dev.off() must run before stamp_logo_png() re-reads the file. Flush the # device now and clear the on.exit handler so it does not fire twice. grDevices::dev.off() on.exit() stamp_logo_png(path, ...) } invisible(path) }