Architecture & internals (for contributors)
Source:vignettes/articles/architecture.Rmd
architecture.RmdThis article is the English entry point for people who want to extend or contribute to rtfreporter. It gives the big picture; the two deep-dive documents are linked below (each opens in English with a Japanese toggle).
π Design specification: Internal class design (S3)
Design principles
-
S3 throughout. Every object is a plain S3 list,
with no external OOP framework. Objects are therefore
dput()-able and serialise cleanly withsaveRDS(). -
Zero third-party runtime dependencies.
Imports:holds only packages that ship with R (grDevices,grid,methods,stats,utils). Every optional integration (gt, gtsummary, rtables/tern, flextable, huxtable, ggplot2, β¦) lives inSuggests:and is reached through.need_pkg(), which errors helpfully when the package is absent and warns when it is too old. - twips are the only length unit used internally (1 inch = 1440 twips).
- Immutable, functional helpers. Internal mutators return a new copy of the object rather than mutating in place.
-
βnoneβ means omit. A border side of
"none"emits no RTF command at all, rather than an explicit zero-width border.
Repository layout
rtfreporter/
βββ R/
β βββ pipe-composition.R # public pipe API: rtf_document/section/tables/figures
β βββ rtftable.R # rtftable() S3 object + normalisation helpers
β βββ rtfplot.R # rtfplot() S3 object
β βββ rtfreport.R # internal rtfreport document object + rtf_header/footer
β βββ generate_rtfreport.R # the RTF renderer (S3 doc -> .rtf text)
β βββ rtf_border.R # border hierarchy (S3)
β βββ rtf_table_style.R # shared table style (S3, snapshot semantics)
β βββ col_header.R # unified column-header API (col_cell / rtf_col_header)
β βββ as_rtftables.R # table-object -> list of rtftable pages (the hub)
β βββ as_rtftable.R # single-page convenience wrapper
β βββ gt_adapter.R # gt / gtsummary metadata extraction
β βββ rtables_adapter.R # rtables / tern metadata extraction
β βββ paginate.R # (deprecated) pagination generic + internal core
β βββ blank_rows.R # blank-row specifications + attribute reader
β βββ ...
βββ inst/resources/ # admin-tunable RTF command templates & defaults
βββ tests/testthat/ # the test suite (S3, edition 3)
βββ vignettes/ # user guides (root) + articles/ (site-only)
The object model
rtf_document (public S3, built by the pipe API)
βββ contents[] one entry per page: rtftable | rtfplot
βββ titles[] per-page title block (character vector)
βββ footnotes[] per-page footnote block
βββ sections[] header/footer per page-range (via from_page)
rtftable one table (single- or multi-data.frame)
βββ data / data_list
βββ col_header(_list) character vector | stacked spanning rows
βββ col_spec per-column align / bold / italic / indent / border
βββ cell_styles per-cell overrides (bold/italic/underline/indent)
βββ border rtf_table_border (zones)
βββ width / height / padding / valign fields
rtfplot one embedded PNG/JPEG figure
rtf_document is the public, pipe-friendly surface.
Internally it is converted to an rtfreport object by
.pipe_doc_to_rtfreport(), validated by
.rtfreport_validate(), and handed to the renderer.
The public classes and their constructors
| Class | Constructor(s) | Role |
|---|---|---|
rtf_document |
rtf_document() |
The document being assembled (pages + sections + document-wide settings). |
rtftable |
rtftable(), as_rtftable(),
as_rtftables()
|
One table (or one page of a paginated table). |
rtfplot |
rtfplot() |
One embedded figure. |
rtf_border |
rtf_border(), TRUE,
rtf_border(top = TRUE) (the older
rtf_border_top() / _bottom() /
_box() / _none() /
rtf_border_with() are deprecated) |
A border for a selection: its outer edges plus the rules between the
rows (inside_h) and between the cells
(inside_v) inside it. What it applies to is decided by
where you attach it. |
rtf_table_border |
rtf_table_border(), rtf_border_tfl() β
both deprecated
|
The five-zone map. Superseded: attach an rtf_border()
to rtftable(border = ) for the whole table, or to
style_zone() for one kind of row; the TFL preset is
border = "tfl" or rtf_table_style_tfl(). |
rtf_table_style |
rtf_table_style(), rtf_table_style_with(),
rtf_table_style_tfl()
|
A reusable style (borders + padding + row-height defaults), captured by snapshot at construction. |
rtf_col_header |
rtf_col_header(), col_cell()
|
Multi-row / spanning column headers. |
Not every public function is a constructor. rtf_config()
is a configurator, not a constructor: it takes an
rtf_document, updates only the non-NULL
fields, and returns a copy of the same
rtf_document class (it sets no new class) β just like the
other pipe verbs (rtf_tables(), rtf_section(),
β¦). Likewise rtf_header() / rtf_footer()
return plain (unclassed) lists describing a sectionβs header / footer:
to change a row, make it again.
rtf_documentvsrtfreport.rtf_documentis the public object you build and hold via the pipe. It is converted to a separate internal class,rtfreport, only at render time (bygenerate_rtfreport()); see Internal class design (S3). You do not construct or handlertfreportdirectly.
print() on an rtftable previews the
laid-out cells in the console (format() gives those lines,
summary() the metadata); plot() draws a
wireframe of a tableβs borders or of the document.
The rendering pipeline
rtf_document() |> rtf_section() |> rtf_tables() / rtf_figures()
β
βΌ .pipe_doc_to_rtfreport() (adapter: pipe API -> rtfreport)
rtfreport
β .rtfreport_validate() (auto-default section, checks)
βΌ
generate_rtfreport(doc, "out.rtf") (renderer in generate_rtfreport.R)
β .render_rtftable() / .render_rtfplot()
βΌ
.rtf text file
The renderer is pure string assembly driven by templates in
inst/resources/rtf_commands.R; size/spacing defaults live
in inst/resources/rtfreporter_defaults.R so they can be
tuned without touching code.
The table-object hub: as_rtftables()
as_rtftables() is
the single entry point that turns an external table object into
a list of rtftable pages. It supports gt_tbl,
gtsummary tables, rtables/tern
(VTableTree), and plain data.frame/tibble (or
a list of any of these).
Every source is reduced to one common intermediate β a kwargs list β which the rest of the pipeline consumes identically:
list(
data = <data.frame>, # the rendered body
col_header = <chr | stacked spanning rows>,
col_spec = <list(list(col=, align=, ...), ...)>,
cell_styles = <list per row, or NULL>,
titles_block = <chr | NULL>, # -> page title
footnotes_block = <chr | NULL> # -> page footnote
)gt_adapter.R and rtables_adapter.R each
produce exactly this shape. as_rtftables() then
(optionally) sorts the body rows before the split
(sort_by / sort_desc, so grouping sees the
ordered rows), paginates the body (.paginate_df()), slices
cell_styles per page, replicates the shared
header/width/spanning metadata onto every page, and attaches the
title/footnote blocks as the rtf_titles /
rtf_footnotes attributes that rtf_tables()
reads. A column can be carried through the split for grouping/ordering
and then hidden from the rendered pages
(drop_cols): the columns stay present for
.paginate_df(), then .apply_col_drop() removes
them and reindexes every position-indexed argument (header
incl.Β spanning, col_spec, widths, row_title,
cell_styles) to the remaining columns just before each
pageβs rtftable() is built.
Because the adapters all converge on one shape, adding a new source type is a small, well-contained job β see Adding a table-object adapter.
Where to read more
- Per-function reference: the Reference index.
- The internal class design and helper catalogue: Internal class design (S3).
- Contribution workflow and code style: CONTRIBUTING.md.