feat(flextable): reusable Civilytics flextable branding helpers #10

Merged
jared merged 1 commits from feat/flextable-branding into feat/stamp-logo-png 2026-06-22 17:00:09 -04:00
Owner

Stacked on #9

This PR is stacked on #9 (feat/stamp-logo-png), because save_branded_flextable_png() calls stamp_logo_png(), which only exists on that branch. Retarget this PR to master once #9 merges.

What this adds

Two small, reusable functions in new file R/flextable.R that generalize the flextable brand styling + "export to PNG + stamp logo" dance repeated across Civilytics projects:

  • style_flextable_civilytics(ft, ...) — applies only the visual brand to an already-structured flextable: header fill #2c3e50 / white bold text / 14pt title line, 11pt Arial body, zebra striping on even body rows (#f0f0eb, guarded for tables with < 2 rows), outer #888888 + inner-horizontal #cccccc borders, footer 9pt #555555 (only if a footer part exists), and layout = "fixed". Every value is an overridable parameter; zebra = TRUE toggles striping. Scope discipline: structure (labels, header lines, widths, alignment, footer text) stays with the caller — this styles only.
  • save_branded_flextable_png(ft, path, logo = TRUE, res = 300, extra_height = 0.4, ...) — sizes a ragg::agg_png() device to flextable_dim(ft) plus extra_height headroom, plot(ft), closes the device, then (if logo) calls stamp_logo_png(path, ...) with ... forwarding type/variant/position/etc. Returns path invisibly.

Intended caller usage (callers not changed here)

ft <- flextable(data) |> set_header_labels(...) |> add_header_lines("…") |>
      width(...) |> align(...) |> add_footer_lines("Source: …") |>
      style_flextable_civilytics()
save_branded_flextable_png(ft, "table.png")
knitr::include_graphics("table.png")

Suggests-guard design

flextable, officer, and ragg are added to Suggests (not Imports) to keep the base install light. Accordingly, no @importFrom for them — everything is referenced fully qualified (flextable::, officer::, ragg::) and each function guards at the top with requireNamespace(..., quietly = TRUE) + an install hint. stamp_logo_png() is in-package (called directly); grDevices is base.

Test evidence

tests/testthat/test_flextable.R — real round-trip tests (not skip-only; guarded with skip_if_not_installed() for portability, base tempfile() + on.exit(unlink())):

  • style_flextable_civilytics() returns a flextable and yields layout = "fixed"
  • idempotent on a 1-row table (no zebra error on re-styling)
  • zebra = FALSE still valid
  • save_branded_flextable_png() writes a PNG with plausible pixel dimensions
  • logo = FALSE skips stamping but still writes
  • larger extra_height produces a taller PNG
[ FAIL 0 | WARN 429 | SKIP 0 | PASS 13 ]

(Warnings are harmless Arial font-substitution messages from headless rendering.)

Files changed

R/flextable.R, man/style_flextable_civilytics.Rd, man/save_branded_flextable_png.Rd, tests/testthat/test_flextable.R, DESCRIPTION (Suggests), NAMESPACE (two exports). Pre-existing roxygen doc drift in the repo was reverted to keep this PR focused.

Follow-up (separate, after a civilytics release): swap the 6 table chunks in crdc-arrests/social_media_posts.qmd to use these helpers.

## Stacked on #9 This PR is **stacked on #9** (`feat/stamp-logo-png`), because `save_branded_flextable_png()` calls `stamp_logo_png()`, which only exists on that branch. **Retarget this PR to `master` once #9 merges.** ## What this adds Two small, reusable functions in new file `R/flextable.R` that generalize the flextable brand styling + "export to PNG + stamp logo" dance repeated across Civilytics projects: - **`style_flextable_civilytics(ft, ...)`** — applies *only* the visual brand to an already-structured flextable: header fill `#2c3e50` / white bold text / 14pt title line, 11pt Arial body, zebra striping on even body rows (`#f0f0eb`, guarded for tables with < 2 rows), outer `#888888` + inner-horizontal `#cccccc` borders, footer 9pt `#555555` (only if a footer part exists), and `layout = "fixed"`. Every value is an overridable parameter; `zebra = TRUE` toggles striping. **Scope discipline:** structure (labels, header lines, widths, alignment, footer text) stays with the caller — this styles only. - **`save_branded_flextable_png(ft, path, logo = TRUE, res = 300, extra_height = 0.4, ...)`** — sizes a `ragg::agg_png()` device to `flextable_dim(ft)` plus `extra_height` headroom, `plot(ft)`, closes the device, then (if `logo`) calls `stamp_logo_png(path, ...)` with `...` forwarding `type`/`variant`/`position`/etc. Returns `path` invisibly. ### Intended caller usage (callers not changed here) ```r ft <- flextable(data) |> set_header_labels(...) |> add_header_lines("…") |> width(...) |> align(...) |> add_footer_lines("Source: …") |> style_flextable_civilytics() save_branded_flextable_png(ft, "table.png") knitr::include_graphics("table.png") ``` ## Suggests-guard design `flextable`, `officer`, and `ragg` are added to **Suggests (not Imports)** to keep the base install light. Accordingly, no `@importFrom` for them — everything is referenced fully qualified (`flextable::`, `officer::`, `ragg::`) and each function guards at the top with `requireNamespace(..., quietly = TRUE)` + an install hint. `stamp_logo_png()` is in-package (called directly); `grDevices` is base. ## Test evidence `tests/testthat/test_flextable.R` — real round-trip tests (not skip-only; guarded with `skip_if_not_installed()` for portability, base `tempfile()` + `on.exit(unlink())`): - `style_flextable_civilytics()` returns a `flextable` and yields `layout = "fixed"` - idempotent on a 1-row table (no zebra error on re-styling) - `zebra = FALSE` still valid - `save_branded_flextable_png()` writes a PNG with plausible pixel dimensions - `logo = FALSE` skips stamping but still writes - larger `extra_height` produces a taller PNG ``` [ FAIL 0 | WARN 429 | SKIP 0 | PASS 13 ] ``` (Warnings are harmless Arial font-substitution messages from headless rendering.) ## Files changed `R/flextable.R`, `man/style_flextable_civilytics.Rd`, `man/save_branded_flextable_png.Rd`, `tests/testthat/test_flextable.R`, `DESCRIPTION` (Suggests), `NAMESPACE` (two exports). Pre-existing roxygen doc drift in the repo was reverted to keep this PR focused. > Follow-up (separate, after a civilytics release): swap the 6 table chunks in `crdc-arrests/social_media_posts.qmd` to use these helpers.
jared added 1 commit 2026-06-22 16:54:01 -04:00
feat(flextable): add reusable Civilytics flextable branding helpers
R-CMD-check / R CMD check (pull_request) Successful in 4m20s
4f51a49903
Generalize the flextable brand styling and "export to PNG + stamp logo"
pattern repeated across Civilytics projects into two small functions:

- style_flextable_civilytics(): applies only the visual brand (header
  fill/color, body font/size, zebra striping guarded for <2 rows,
  borders, footer styling, fixed layout) to an already-structured
  flextable. Every value is an overridable parameter; zebra toggles
  striping. Structure (labels, headers, widths, alignment, footer text)
  stays with the caller.
- save_branded_flextable_png(): exports a styled flextable to PNG via
  ragg::agg_png() sized to the table plus extra_height headroom, then
  optionally stamps the logo via stamp_logo_png() (... forwarded).

flextable, officer, and ragg are added to Suggests (not Imports) to keep
the base install light; both functions reference them fully qualified and
guard with requireNamespace() + an install hint. Real round-trip tests
cover styling, 1-row idempotence, PNG export, and extra_height.
jared merged commit 2743ebb09c into feat/stamp-logo-png 2026-06-22 17:00:09 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Civilytics/civilyticsR#10