fix: resolve locked namespace binding in civilytics_load_fonts() and default theme to transparent background
R-CMD-check / R CMD check (pull_request) Failing after 4m13s

- Replace .cv_fonts_loaded <<- TRUE with environment-based state
  (.cv_state) to avoid 'locked namespace binding' error (#18)
- Add force=FALSE argument for idempotent font loading with guard check
- Flip theme_civilytics() paper_bg default to FALSE (transparent) (#7)
- Update roxygen docs and examples for both changes
This commit is contained in:
2026-08-01 02:07:09 -04:00
parent 0ef0f310e2
commit e257180f15
2 changed files with 34 additions and 19 deletions
+24 -11
View File
@@ -5,32 +5,45 @@ CV_FONT_SANS <- "Inter" # axis text, legends, UI elements
CV_FONT_SERIF <- "Source Serif 4" # body prose / editorial long-form CV_FONT_SERIF <- "Source Serif 4" # body prose / editorial long-form
CV_FONT_MONO <- "JetBrains Mono" # code, data tables, numeric callouts CV_FONT_MONO <- "JetBrains Mono" # code, data tables, numeric callouts
# Internal flag so civilytics_load_fonts() is idempotent within a session. # Mutable package state held in an environment so the binding itself stays
.cv_fonts_loaded <- FALSE # locked (R locks all namespace bindings at load time) while the contents
# remain writable. See https://adv-r.hadley.nz/environments.html#environments-as-containers
.cv_state <- new.env(parent = emptyenv())
.cv_state$fonts_loaded <- FALSE
#' Load Civilytics brand fonts #' Load Civilytics brand fonts
#' #'
#' Downloads Inter and Libre Franklin from Google Fonts via #' Downloads Inter, Libre Franklin, Source Serif 4 and JetBrains Mono from
#' [sysfonts::font_add_google()], then calls [showtext::showtext_auto()] so #' Google Fonts via [sysfonts::font_add_google()], then calls
#' that all graphics devices render text with those fonts. This is called #' [showtext::showtext_auto()] so that all graphics devices render text with
#' automatically when the package loads; use this function to retry if the #' those fonts. This is called automatically when the package loads; use this
#' initial load failed (e.g., the machine was offline at load time). #' function to retry if the initial load failed (e.g., the machine was offline
#' at load time).
#' #'
#' @return Invisibly returns `NULL`. #' Subsequent calls within the same session are no-ops unless `force = TRUE`.
#'
#' @param force Logical. If `TRUE`, reload fonts even if they were already
#' loaded in this session. Default `FALSE`.
#'
#' @return Invisibly returns `TRUE` if fonts were loaded, `FALSE` if skipped
#' (already loaded and `force = FALSE`).
#' @export #' @export
#' #'
#' @examples #' @examples
#' \dontrun{ #' \dontrun{
#' civilytics_load_fonts() #' civilytics_load_fonts()
#' } #' }
civilytics_load_fonts <- function() { civilytics_load_fonts <- function(force = FALSE) {
if (.cv_state$fonts_loaded && !force) return(invisible(FALSE))
sysfonts::font_add_google("Inter", family = "Inter") sysfonts::font_add_google("Inter", family = "Inter")
sysfonts::font_add_google("Libre Franklin", family = "Libre Franklin") sysfonts::font_add_google("Libre Franklin", family = "Libre Franklin")
sysfonts::font_add_google("Source Serif 4", family = "Source Serif 4") sysfonts::font_add_google("Source Serif 4", family = "Source Serif 4")
sysfonts::font_add_google("JetBrains Mono", family = "JetBrains Mono") sysfonts::font_add_google("JetBrains Mono", family = "JetBrains Mono")
showtext::showtext_auto() showtext::showtext_auto()
.cv_fonts_loaded <<- TRUE
invisible(NULL) .cv_state$fonts_loaded <- TRUE
invisible(TRUE)
} }
.onLoad <- function(libname, pkgname) { .onLoad <- function(libname, pkgname) {
+10 -8
View File
@@ -36,10 +36,12 @@
#' to [civilytics_colors]`["paper_2"]` (`#F2EDE4`). #' to [civilytics_colors]`["paper_2"]` (`#F2EDE4`).
#' @param grid Character. Which major gridlines to draw: `"y"` (default, #' @param grid Character. Which major gridlines to draw: `"y"` (default,
#' horizontal only), `"x"` (vertical only), `"both"`, or `"none"`. #' horizontal only), `"x"` (vertical only), `"both"`, or `"none"`.
#' @param paper_bg Logical. If `TRUE` (default), fill the plot and panel #' @param paper_bg Logical. If `TRUE`, fill the plot and panel backgrounds
#' backgrounds with the warm `paper` color. Set to `FALSE` for a #' with the warm `paper` color (Civilytics cream). Default is `FALSE`
#' transparent background (useful for slides or overlay on colored #' (transparent) so that figures composite cleanly onto any background.
#' surfaces). #' Set to `TRUE` for the branded cream canvas. Note: a transparent device
#' background (e.g., `dev = "ragg_png"`, `dev.args = list(background =
#' "transparent")`) is also needed for fully-transparent PNG exports.
#' #'
#' @section Font size hierarchy: #' @section Font size hierarchy:
#' All text sizes are derived from `font_size` using relative scale factors. #' All text sizes are derived from `font_size` using relative scale factors.
@@ -68,7 +70,7 @@
#' \dontrun{ #' \dontrun{
#' library(ggplot2) #' library(ggplot2)
#' #'
#' # Default editorial theme #' # Default — transparent background for embedding
#' ggplot(mpg, aes(displ, hwy)) + #' ggplot(mpg, aes(displ, hwy)) +
#' geom_point() + #' geom_point() +
#' theme_civilytics() #' theme_civilytics()
@@ -79,10 +81,10 @@
#' scale_color_civilytics() + #' scale_color_civilytics() +
#' theme_civilytics(grid = "both") #' theme_civilytics(grid = "both")
#' #'
#' # Transparent background for embedding #' # Branded cream background (opt-in)
#' ggplot(mpg, aes(displ, hwy)) + #' ggplot(mpg, aes(displ, hwy)) +
#' geom_point() + #' geom_point() +
#' theme_civilytics(paper_bg = FALSE) #' theme_civilytics(paper_bg = TRUE)
#' #'
#' # Larger text for poster or display #' # Larger text for poster or display
#' ggplot(mpg, aes(displ, hwy)) + #' ggplot(mpg, aes(displ, hwy)) +
@@ -102,7 +104,7 @@ theme_civilytics <- function(
accent = unname(civilytics_colors["ember_600"]), accent = unname(civilytics_colors["ember_600"]),
strip_color = unname(civilytics_colors["paper_2"]), strip_color = unname(civilytics_colors["paper_2"]),
grid = c("y", "x", "both", "none"), grid = c("y", "x", "both", "none"),
paper_bg = TRUE) { paper_bg = FALSE) {
grid <- match.arg(grid) grid <- match.arg(grid)
half_line <- font_size / 2 half_line <- font_size / 2