as_rtftables() is the single entry point for turning a table object
into a list of rtftable() objects – one per RTF page – ready to hand
straight to rtf_tables(). It unifies two jobs that used to be split
across paginate() (page splitting) and rtf_tables(read_gt = )
(metadata extraction):
Usage
as_rtftables(x, ...)
# Default S3 method
as_rtftables(
x,
read_meta = TRUE,
max_rows = NULL,
split = c("none", "rows", "group_safe", "group_force", "by_value"),
split_rows = NULL,
group_col = NULL,
group_by = c("auto", "indent", "value", "filled"),
page_by = NULL,
sort_by = NULL,
sort_desc = NULL,
cont_label = " (Cont.)",
min_group_rows = 2L,
blank_rows = NULL,
blank_row_first = FALSE,
blank_row_end = FALSE,
count_blank_rows = FALSE,
align_count_pct = FALSE,
cell_format = NULL,
na = "",
collapse_repeats = NULL,
drop_cols = NULL,
listing = NULL,
stub = NULL,
stub_vars = NULL,
stub_label = NULL,
stub_indent = 4L,
stub_group_summary = c("empty", "parent"),
header_sep = .default_header_seps(),
auto_width = FALSE,
table_width_twips = NULL,
border = "tfl",
style = NULL,
...
)Arguments
- x
A
gt_tbl, agt_group, a gtsummary table (ortbl_splitcontainer), an rtables/ternVTableTree, an rlistingslisting_df, aflextable, ahuxtable, adata.frame/ tibble, or alistof these.- ...
Further arguments forwarded to
rtftable()for every page (e.g.col_header,col_spec,row_title,col_rel_width,row_height_twips).row_titlenames the row-heading columns (default: column 1) and sets the per-column default alignment (heading columns left, others centre). Explicit values always win over the gt-extracted ones.A user-supplied
col_headeris resolved against the final printed columns – afterstub_varsfolding anddrop_colsremoval – so itscol_cell()positions and names refer to the columns you actually see (position 1 = first printed column; thestub_varsstub is that column). This is equivalent to building the pages and then callingset_col_header(); usertf_columns()to list the final names first. (col_spec/col_rel_width/row_titlestill use the pre-drop input coordinates.)- read_meta
Controls metadata extraction from the source table:
TRUE(default, read everything in the table above),FALSE(use only the rendered body – equivalent to the oldpaginate()), or a character vector of tokens. Tokens for gt/gtsummary:"col_header","alignment","spanning","widths","titles","footnotes","styles"(explicittab_style()borders and text styles; see What is carried). For rtables/tern:"col_header","alignment","spanning","titles","footnotes","indent","footnote_marks"; rlistings takes the same tokens, because the two share oneMatrixPrintFormreader. For flextable:"col_header","alignment","spanning","titles","footnotes". For huxtable:"col_header","alignment","spanning","titles". For a plain data.frame / tibble the single token is"labels": a column'slabelattribute (the haven / labelled / xportr convention) is used as its header label (columns without one keep their name). Unknown tokens are ignored for data.frames – alist()mixing table objects and data.frames shares oneread_meta, so adapter tokens must not error on the data.frame elements.- max_rows
Integer or
NULL. Maximum body rows per page for the"group_safe"/"group_force"splits (required by them). This is the setting for fixed-size pages –max_rows = 25gives pages of 25 rows. Ignored by"none","rows"(which cuts at the positions insplit_rows) and"by_value".- split
How to break the body into pages. A strategy name:
"none"(default) one page; no row limit checked.
"rows"cut before each row position given in
split_rows; requiressplit_rows. For fixed-size pages use"group_safe"/"group_force"withmax_rows."group_safe"pack whole groups onto a page: a group that does not fit in what is left starts the next page instead. A group that on its own exceeds
max_rowshas to be split, and is, with a continuation label. Requiresmax_rows. This is the one that keeps a group together."group_force"cut every
max_rowsrows, wherever that falls: a group straddling the cut is split and the next page opens with a continuation label repeating its heading. Pages come out the same height, which is why a report asks for it — but it will split a group that would have fitted whole on the next page, so it is not"group_safe"with an escape hatch. Onlymin_group_rowspulls a cut back, and only to avoid a widow or an orphan. Requiresmax_rows.
html
Ten groups of four rows with `max_rows = 30` and the blanks counted: `"group_safe"` gives 26 and 26 rows, `"group_force"` gives 30 and 24 with the sixth group cut in half. \item{`"by_value"`}{one page per distinct value of `group_col`; the pages are named by that value. A page gathers every row with its value, scattered or not, in the body's own order.} htmlsplitmay also be a custom function for bespoke page-break rules. It is called assplit(df, max_rows = , group_col = , group_by = , cont_label = , min_group_rows = )on the (cell-formatted) body and must return a non-empty list of data.frames – one per page. Named list elements become page names (as with"by_value"). Your function implements only the split; the shared pipeline (blank rows, metadata, per-page assembly, and header / width / style replication) is applied to its output unchanged. Write the function with a...so it tolerates the context arguments it does not use, and seeadd_cont_label()for re-creating the" (Cont.)"continuation row. A plan passes it the same way:plan_paginate_rows(split = f).- split_rows
Integer vector or
NULL. Row positions to cut before, forsplit = "rows"(required by it; ignored otherwise).split_rows = c(20, 40)yields pages1:19,20:39,40:nrow. It is not a page size: for pages of a fixed number of rows usemax_rowswithsplit = "group_safe"or"group_force".- group_col
Character, integer, or
NULL. The column the group-aware splits ("group_safe","group_force","by_value") detect groups on, given by name or position.NULL(default) uses column 1. This selects only the column; how a group boundary is found on it is set bygroup_by. Note that for gt / gtsummary the body keeps gt's column ids (e.g."label","stat_1"), and for rtables / flextable / huxtable the columns are renamedV1,V2, ... – so an integer index is the most portable. A factor group column is rendered as its labels: it is coerced to character just before the split, so the group-aware splits can write thecont_labelsuffix into it. This happens aftersort_by, which still orders a factor by its levels.- group_by
How a group boundary is found on
group_col:"auto"(default) pick from the column content: leading indentation present ->
"indent"; else interspersed empty cells ->"filled"; else ->"value"."indent"a row starts a group when its
group_colcell is non-empty and does not begin with whitespace (space / tab / non-breaking space); indented or empty cells are members. The typical clinical row-label layout (gt / tfrmt bake indentation as NBSP)."value"each maximal run of rows sharing the same
group_colvalue is one group."filled"a row starts a group when its
group_colcell is non-empty; onlyNA/""cells are members (the label appears once, on the group's first row).
- page_by
Column(s) whose value makes a page and names it, or
NULL(default, off). The body is partitioned by thepage_byvalue(s) first, and every other pagination setting –split,max_rows,group_col,group_by,min_group_rows,cont_label,blank_rows,collapse_repeats– then applies within one partition. This is the two-level clinical layout: a BY variable (period, cohort, analysis set) owns the page, while an inner group (a parameter block) is kept whole inside it.as_rtftables(lab, page_by = "period", # a page per period, named by it split = "group_safe", # from here on: inside one period group_by = "indent", max_rows = 20, drop_cols = "period")- Page names
the outermost value alone, carried by every page it makes: the
page_byvalue, or – when the split names its own pages ("by_value") and the group is therefore the outer axis – the group's. The two are never joined. A page name is the headingrtf_tables(auto_section = TRUE)/auto_title = TRUEprints and the line a section breaks on, so joining them would cut one group into a section per BY value; both values stay readable in the page'srtf_paginate_meta(page_group/page_by). Pages that share a heading are numbered with a"...n"tail –"Period 1...1","Period 1...2"– so the list stays addressable, R returning the first match for a repeated list name (which is whatpages[["Period 1"]]and RStudio'sView()would show). The tail is not part of the heading:auto_section/auto_titlestrip it, so the printed heading is"Period 1"and its pages are one section. A heading used once carries no tail.group_colwhen left
NULL, defaults to the first column not named inpage_by, sogroup_by = "indent"(which reads the group column) never lands on the BY column – where every row of a partition carries the same value and there is no structure to find.- Order of operations
page_bycuts pages, thensplitcuts rows inside each one, thenpaginate_cols()cuts columns on the result (itspage_orderdecides the final page sequence). Withsplit = "by_value"the group is the outer axis andpage_bythe inner one – a group's pages stay together (and share its name), the BY values running in order inside it – so the three come out asG/C/Punderpage_order = "across"andG/P/Cunder"down".- Per partition
split_rowspositions,blank_rowspositions (0/-1included),count_blank_rowsaccounting and anyrtf_blank_rowsattribute on the input are all resolved inside the partition.naandcell_format, by contrast, run body-wide before the partition, so one column width is shared by every page.- A page gathers its rows
a value that comes back later in the body belongs to the page it names, not to a new one, and the rows keep the order the body had – inside a page and between pages.
split = "by_value"readsgroup_colthe same way. Needing the body sorted first would be SAS's contract, not R's, so a merely interleaved body loses nothing; usesort_bywhen you want an order, not to make the pages whole.
page_bydoes not implydrop_cols: name the column there too to keep it out of the printed table. Likegroup_col/sort_by/drop_colsit addresses the body after astubreshape, so a column the stub consumed cannot be apage_bykey (usesplit = "by_value", which splits before the stub is built). Withpage_by = NULLnothing changes anywhere –"by_value"still takes its page names fromgroup_col.- sort_by
Columns to order the body rows by, before pagination, or
NULL(default, keep the input order). A character / integer vector (or alist()to mix names and indices) in the input body's coordinates – the same space asgroup_col,collapse_repeatsanddrop_cols. The sort is applied first, so group detection,(Cont.)labels andblank_rowspositions all see the sorted order. A sort key may also be listed indrop_colsto order on a column that is not printed (the classic hidden "sort-key" carrier column). The sort is stable (rows that compare equal keep their input order) andNAkeys sort last regardless ofsort_desc. Note thatsort_byreorders rendered body rows as-is, so it suits flat / tabular bodies (adata.frame, or a pre-flattened table); on a gt / rtables body whose group-label and child rows are already interleaved it would scramble that visual hierarchy – sort the source data before building such a table instead.- sort_desc
Sort direction for
sort_by:NULL/FALSE(default, all ascending), a singleTRUE(allsort_bycolumns descending), or a logical vector the same length assort_by(one direction per key). Ignored whensort_byisNULL.- cont_label
Character (default
" (Cont.)"). Suffix appended to a group's label on the second and later pages it continues onto (the group-aware splits), marking a continued group.- min_group_rows
Integer (default
2). Widow/orphan control for the group-aware splits ("group_force","group_safe","by_value"): when a page would end on a group that starts on that page while showing fewer thanmin_group_rowsof the group's child rows, the whole group is moved to the next page. This prevents a lone group header being stranded at the foot of a page with none (or too few) of its members. Set to0to disable (the previous behaviour).- blank_rows
Where to insert blank separator rows in the body.
NULL(default) inserts none (but anrtf_blank_rowsattribute already on the input is still honoured). Accepts any of – or alist()combining:- an integer vector of positions
a blank row is inserted after each given data-row index;
0= before the first row,-1= after the last row (e.g.c(0, 5, -1))."between_groups"a blank at every group transition on
group_col, using this call'sgroup_bydetection (auto / indent / value / filled).- a
blank_rows_by_change()object insert a blank whenever the group of one or more columns changes – the spec carries its own
group_by.- a
blank_rows_by_rule()object insert a blank before / after rows whose column matches a regular expression.
For example
blank_rows = list(c(-1), blank_rows_by_change("Visit"))adds a trailing blank and a blank at every change ofVisit. By default these blanks are added after the split and do not count towardmax_rows; setcount_blank_rows = TRUEto makemax_rowsmean the rows the page actually prints.- blank_row_first, blank_row_end
Logical (default
FALSE). Add a single blank row at the very top (blank_row_first) or bottom (blank_row_end) of every page, as page furniture. They are applied after the split, and whether they count towardmax_rowsiscount_blank_rows: underTRUEthey do, somax_rowsis the number of rows the page prints; underFALSE(the default) they do not, and the printed page can be up to two rows taller thanmax_rows.- count_blank_rows
Logical (default
FALSE). Whatmax_rowscounts.TRUEmax_rowsis the number of rows the page prints: body rows, blank rows already in the data, the blanksblank_rowsinserts between groups, and theblank_row_first/blank_row_endpage edges. A page never overflows the budget. The edges are counted as they actually render: a page-edge blank merges with a blank row it sits against (seeblank_row_normalize), so it costs a row only where there is no blank row there already – which is why a listing, whose every record block ends in a blank row, does not lose a row at the foot of each page.FALSE(default)
max_rowscounts only the rows that were in the input. Nothing inserted counts, at the page edges or between groups, so the printed page can be taller thanmax_rows.
Under
TRUEthe blank positions resolved fromblank_rows(and from anyrtf_blank_rowsattribute already on the input) are materialised before the split and re-attached per page afterwards, with a leading blank suppressed at the top of each page.- align_count_pct
The cell alignment to apply before pagination.
FALSE(default) none;TRUEthe built-in cell format"count_pct", which realigns"n (xx.x)"count/percent cells to a uniform width. It also accepts the name of any built-in ("count_paren","value_paren","right_align", ... – thefmt_prefix is optional) or a function of your own, in which case it behaves exactly likecell_format. Ignored whencell_formatis supplied, which takes precedence. Neither one controls whether missing values are substituted – that isna– only whether the substituted token is aligned with the counts.- cell_format
Optional cell re-formatter applied column-by-column to the body before pagination, for monospaced alignment. Either one formatter – applied to every data column (columns 2..N; the row-label column 1 is left alone) – or a list of them taken positionally (
cell_format[[j]]for columnj; entries that are neither a function nor a name are skipped).A formatter is a function taking one column (a character vector) and returning a character vector of the same length, or the name of a built-in as a string –
"right_align","count_paren","count_paren_bare","value_paren","count_pct", with thefmt_prefix optional, so"fmt_count_paren"works too:as_rtftables(x, cell_format = "value_paren") as_rtftables(x, cell_format = list(NULL, "right_align", my_fmt))See
fmt_count_paren()/fmt_value_paren()/fmt_right_align()for what each one does, and the contract in?fmt_right_alignfor writing your own. When supplied it takes precedence overalign_count_pct.- na
Text to print for a missing value (default
"", an empty cell – the previous behaviour). Applied to every column, the row-label column included, and independently ofalign_count_pct/cell_format– those decide whether the substituted token is also aligned, not whether the substitution happens. With one of them set, the token is right-justified in the count field so its right edge lands on the ones digit:Text already in the data that is not missing (
"NE","n/a", a literal"-"you typed yourself) does not match the count pattern and passes through unchanged, with no padding.NaNcounts as missing (R's ownis.na()says so, and in a TFL aNaNis a0/0percentage).Inf/-Infdo not: they print as"Inf"/"-Inf", because an infinity means a division by zero upstream and rendering it as"-"would hide the bug. The strings"NA"/"NaN"are never touched –"NA"can be legitimate data.The substitution happens before the split, so the
NAscollapse_repeatswrites per page to blank out repeated values stay blank.- collapse_repeats
Columns in which to blank consecutive repeated values (repeat suppression), or
NULL(default, off). A character / integer vector naming the columns in priority order. Within each column, only the first value of a run is kept; the rest of the run is replaced withNA(which renders as an empty cell – no row is removed, only the display text is suppressed). When several columns are given the suppression is hierarchical: the first column is collapsed on its own value, and each later column on the combination of itself with all earlier listed columns (a change in any higher column restarts the lower column's run). This runs per page, after the split, so the pagination still sees the original repeated values – group boundaries and(Cont.)labels stay correct, and a group continued onto the next page shows its label again at the top. (Ingroup_byterms: value-based grouping happens first, then the column is collapsed to a"filled"-style display.)- drop_cols
Columns to hide from the printed table while still using them for pagination / grouping, or
NULL(default, drop nothing). A character / integer vector (or alist()to mix names and indices) naming the columns in the input body's coordinates – the same coordinate space asgroup_colandcollapse_repeats. The named columns stay present through the split (sogroup_col,collapse_repeatsandblank_rows_by_change()can reference them), then are removed from every page before that page's table is rendered. This makes a column usable as a hidden grouping / sort-key / carrier column without it appearing in the report. Position-indexed metadata (col_spec, column widths,col_header_align,row_title, and per-cellcell_styles) is reindexed automatically to the remaining columns. A user-suppliedcol_headeris the exception: it is applied against the final printed columns (see thecol_headerargument andset_col_header()), so you write its positions / names for the columns that remain – not the pre-drop layout.drop_colsmust leave at least one column to display. Note that forsplit = "by_value"the page names come from thegroup_colvalue, so that column may be dropped and the pages are still named by it; but under"group_safe"/"group_force"the" (Cont.)"marker is written into thegroup_colcell, so ifgroup_colis itself dropped the marker is not shown (group on the visible label column if the marker is wanted).- listing
Listing settings, as a
listing_spec()object, orNULL(default, no listing preparation). Applies to a data.frame / tibble source:build_listing()reshapes it into a listing body – source variables joined into their printed columns, long cells wrapped over several physical rows, gutter columns, a blank row after each record – and the spec's columns then supply thecol_header, the relative widths and the (left) alignment, so none of the three has to be written out by hand. The hidden record column is used to keep a record whole across a page break:group_colpoints at it,splitbecomes"group_safe"whenmax_rowsis set, and it is added todrop_cols. Every one of those is a default – an argument you pass yourself is never overridden. A body that has already been throughbuild_listing()carries its own spec, soas_rtftables()picks it up and passinglistingas well is an error. An rlistingslisting_dfis rejected: it is already laid out, and needs nolistingargument.- stub
Row-stub settings, as a
stub_spec()object – or, as a shorthand forstub_spec(vars), a bare vector of hierarchy columns (parent first, leaf last; at least two).NULL(default) builds no stub. This is the one argument for everythingstub_cols()can do, includinglayoutandlabel_span, which the deprecatedstub_varsfamily cannot reach. Passing bothstuband any of thestub_varsfamily is an error.The stub is applied to the extracted body – after the table is pulled out of its source, before pagination – so any input that carries the hierarchy as separate columns (a plain
data.frame, agt(df), a multi-group-column table, or a tfrmtrow_grp_plan(location = "column")table) gains a clinical stub. Withlayout = "merged"the named columns are consumed and replaced by one stub column at position 1 and the remaining columns keep their order; withlayout = "columns"every column stays where it is. Either waydrop_cols/group_col/sort_by(plusgroup_by = "indent"detection and the group-aware splits) then refer to the reshaped body. Exception: withsplit = "by_value"the body is split bygroup_colfirst and the stub is built per page afterwards – each value becomes an independent section, sogroup_colhere refers to the pre-stub columns and is not folded into the stub. This keeps a fixed intermediate hierarchy level (e.g.LBCAT / group1 / labelwith a constantgroup1) from collapsing into a single stub label row that spans every group; put the inner levels in the stub and the outer level ingroup_col. Position-indexed metadata (col_spec,col_header_align, per-cellcell_styles) is reindexed automatically; a user-suppliedcol_headeris instead resolved against the final columns (seeset_col_header()). Sources that already render an indented stub (rtables / tern, tfrmt indented, gtsummary) come out pre-merged, so a stub does not apply to them. Underlayout = "merged"the source column widths are not carried through the merge – useauto_width = TRUEor pass explicit widths;layout = "columns"keeps them.- stub_vars
Deprecated in 0.8.x (warns once a session, still works), removed in 0.9.0: write
stub = c(...)(orstub = stub_spec(...)), which does the same and also reacheslayoutandlabel_span. Hierarchy columns to merge into a single indented stub column (parent first, leaf last; at least two), orNULL(default, no stub built). Appliesstub_cols()to the extracted body – after the table is pulled out of its source, before pagination – so any input that carries the hierarchy as separate columns (a plaindata.frame, agt(df), a multi-group-column table, or a tfrmtrow_grp_plan(location = "column")table) gains a clinical stub. The named columns are consumed and replaced by one stub column at position 1; the remaining columns keep their order, anddrop_cols/group_col/sort_by(plusgroup_by = "indent"detection and the group-aware splits) then refer to the reshaped, post-stub columns. Exception: withsplit = "by_value"the body is split bygroup_colfirst and the stub is built per page afterwards – each value becomes an independent section, sogroup_colhere refers to the pre-stub columns and is not folded into the stub. This keeps a fixed intermediate hierarchy level (e.g.LBCAT / group1 / labelwith a constantgroup1) from collapsing into a single stub label row that spans every group; put the inner levels instub_varsand the outer level ingroup_col. Position-indexed metadata (col_spec,col_header_align, per-cellcell_styles) is reindexed automatically; a user-suppliedcol_headeris instead resolved against the final columns (which already include the stub at position 1 – seeset_col_header()). Sources that already render an indented stub (rtables / tern, tfrmt indented, gtsummary) come out pre-merged, sostub_varsdoes not apply to them. Column widths from the source are not carried through the merge – useauto_width = TRUEor pass explicit widths.- stub_label, stub_indent, stub_group_summary
Deprecated with
stub_vars(removed in 0.9.0): they arestub_spec(label = , indent = , group_summary = ). Forwarded tostub_cols()whenstub_varsis set: the merged stub column's name / header (NULLjoins the merged columns' display names with" / "), the non-breaking spaces per nesting level (default4), and which leaf values fold a row onto its group label row (defaultc("empty", "parent")). Ignored whenstub_varsisNULL.- header_sep
Separator(s) used to reconstruct a spanning (multi-row) column header from a plain data.frame's column names. A bare data.frame carries no spanning metadata, so the nesting is parsed out of the names: each name is split on
header_sepinto segments that become stacked header rows, and horizontally adjacent columns sharing a label and the same ancestor path are merged into one spanning cell. Columns with no separator (e.g. id columns such aslabel) keep their name on the bottom (leaf) row with blank cells above. The default recognizes both built-in delimiters:"____"(fromydisctools::pivot_stats_wider()) and"___tlang_delim___"(tfrmt's column delimiter). Pass your own separator(s) to override, orNULLto disable (use the plainnames(data)single-row header). Only applies to plain data.frame input – gt / gtsummary / rtables / tern / flextable / huxtable already carry real spanning metadata. With the"____"separator a doubled separator ("________") yields an empty middle segment, i.e. a blank cell at that header level (handy to align a column that skips an intermediate spanner level). An explicitcol_header(passed via...) always wins.- auto_width
Logical (default
FALSE). WhenTRUE, each column is sized to its widest content (column header label or data cell) viaauto_col_widths(), so long labels do not wrap. It decides how the columns divide the table, not how wide the table is: the total comes fromtable_width_twips, elsetable_width_pct, else the natural content width capped at the default page's writable width. An explicitcolumn_widths_twipsorcol_rel_widthalways wins over it.- table_width_twips
Total table width in twips, or
NULL(default). This is the same setting asrtftable(table_width_twips = )and means the same thing: the table is exactly this wide, andcol_rel_widthapportions it. It is independent ofauto_width– it says how wide the TABLE is, whileauto_widthdecides how the COLUMNS divide it – and it wins overtable_width_pct, the same total said as a share of the page.NULLleaves the total totable_width_pctif given, and otherwise to the writable page width.The whole vocabulary, in the order it resolves:
column_widths_twipsper column, absolute – wins outright, and the table's total is their sum.
col_rel_widthper column, relative – apportions whatever the total turns out to be.
table_width_twipsthe total, absolute.
table_width_pct/table_width_pct_of_writablethe total, as a share of the writable page width (paper minus the side margins).
- nothing
the writable width, divided equally.
All of them are
rtftable()'s, and mean here exactly what they mean there.- border, style
Passed to
rtftable()for every page.borderdefaults to"tfl".
Value
A list of rtftable objects, one per page. When the split is
value-based (or the input was a named list) the list is named.
Details
Read the table's metadata (only the parts the RTF renderer can use; see What is carried below).
Paginate. The rendered body is split into per-page chunks using the same strategies the old
paginate()offered (split,max_rows,group_col, blank-row controls, ...). The shared header / width / spanning metadata is replicated onto every page.
The page-level title / source-note blocks travel with each returned
rtftable as the attributes rtf_titles / rtf_footnotes, which
rtf_tables() consumes automatically.
Supported inputs: gt_tbl, gt_group (gt's multi-table container from
gt::gt_group() / gt::gt_split() – and what tfrmt's print_to_gt()
returns when the spec has a page_plan; expanded to one page set per
member table), gtsummary tables (including a tbl_split container from
gtsummary::tbl_split_by_rows() / tbl_split_by_columns(), likewise
expanded member by member), rtables/tern VTableTree
tables, rlistings listing_df listings,
flextable tables, huxtable tables, plain data.frame / tibble,
or a list of any of these (the list is flattened, names propagated as
name, repeated on every page it makes). Figures are out of scope – use
rtf_figures() for those.
What is carried, by source
The body is always the table's rendered body – for gt/gtsummary via
gt::extract_body(), for rtables/tern and rlistings via
formatters::matrix_form().
Only visible columns appear (hidden / helper columns such as tfrmt's
..tfrmt_row_grp_lbl are dropped), row-group / stub rows are already
interleaved, and indentation is rendered into the label text. (A gt table
with more than one stub column – e.g. a tfrmt
row_grp_plan(label_loc = element_row_grp_loc(location = "column")) layout –
is read from the table's own _data / _boxhead slots instead, as
gt::extract_body() does not support multiple stub columns; the group and
label columns then come through as ordinary columns.) On top of that body the
following metadata is read:
| Metadata | gt / gtsummary | rtables / tern | flextable | huxtable |
| Column (leaf) labels | yes | yes | yes | yes |
| Per-column alignment | yes | yes | yes | yes |
| Spanning headers | yes | yes | yes | yes |
| Column widths | yes (px/pct) | – | – | – |
| Title + subtitle | yes | yes | yes (caption) | yes (caption) |
| Footnotes / source notes | yes | yes | yes (footer) | – |
| In-cell footnote marks | yes (superscript) | yes (superscript) | – | – |
| Row-group rows + indent | yes (rendered) | yes (rendered) | yes (rendered) | yes (rendered) |
tab_style() cell styles | yes (see below) | – | – | – |
Explicit gt::tab_style() declarations are carried (the "styles"
token, on by default): cell_borders() and the cell_text() bold /
italic / underline / align properties on column spanners, column labels
and body/stub cells, plus body text colour – everything the rtftable
class can hold. A border or underline on a column-label cell promotes
the label row to single-column cells (the same mechanism
style_header() uses). The style_header() / style_body() /
style_cols() verbs still override what was read – adapters populate
first, verbs win per side / per field. Not carried: cell_fill(),
fonts and sizes, header text colour, and gt's theme borders
(tab_options()) – those describe gt's own default look; rtfreporter's
border presets govern the frame instead.
For flextable the displayed text is read (header labels set via
set_header_labels() and colformat_*() formatting included), not the raw
$body$dataset. Cells composed of images / equations and footnote()
reference marks are not carried. For huxtable the displayed text is likewise
read (its number_format applied); huxtable has no footnote concept, so only
the caption (a page title) is carried.
Not carried (RTF cannot reproduce these, or they belong to the
source's own theme): cell background colours / fills, font and size
styling, gt theme borders (tab_options()), header text colour, and
Markdown formatting inside labels or titles. For a plain data.frame / tibble two pieces of
metadata are read: column label attributes become header labels (the
"labels" token, see read_meta), and delimited column names are
reconstructed into a spanning header (see header_sep); everything else
(col_spec, widths, ...) you set yourself. See also stub_cols() for
finishing a tidy hierarchy-column data.frame into the indented stub
layout beforehand.
Superseded arguments
These ... arguments still work and are handy for simple one-shot tables,
but dedicated post-hoc verbs supersede them for anything beyond a single
label row – they address the final printed columns (by name or visible
position), are inspectable, and compose in a pipe:
col_header->set_col_header()(multi-row / spanning headers, name-based;rtf_columns()lists the columns andrtf_header_source()shows the current header as editable source).column text styling / added header rows ->
style_cols(),style_header(),add_header_row().
Prefer the verbs for complex headers; the arguments are kept for convenience and backward compatibility.
Supplying a source from another package
as_rtftables() is an S3 generic. Everything listed above is handled by
the default method, because those packages do not know rtfreporter, so
rtfreporter carries their adapters. A package that does depend on
rtfreporter can make its own class a source by registering a method,
as_rtftables.<class>(x, ...). The method must return what the default
returns: a list of rtftable pages, usually by building a table and calling
as_rtftables() on it. rtf_tables() then accepts the object directly,
converting it the way it converts a gt_tbl. rtfreporter itself names no
such class.
See also
set_col_header() / rtf_columns() / rtf_header_source() and the
styling verbs style_cols() / style_header() / add_header_row() (which
supersede the header / styling ... arguments); as_rtftable() for the
single-page convenience wrapper; rtf_tables() to append the result to a
document.
Examples
# A plain data.frame: one page, no splitting.
df <- data.frame(
Parameter = c("Age", " Mean", " SD", "Sex", " F", " M"),
Value = c("", "75.1", "8.2", "", "53%", "47%"),
stringsAsFactors = FALSE
)
pages <- as_rtftables(df) # length-1 list of rtftable
# A custom page-break rule: the first three rows, then the rest.
three <- function(df, ...) list(df[1:3, ], df[-(1:3), ])
length(as_rtftables(df, split = three)) # 2 pages
#> [1] 2
length(pages)
#> [1] 1
# Cut before the given row positions: pages 1:3, 4:5, 6:6.
pages <- as_rtftables(df, split = "rows", split_rows = c(4, 6))
vapply(pages, function(p) nrow(p$data), integer(1)) # 3 2 1
#> [1] 3 2 1
# Fixed-size pages -- a different setting. `"group_force"` also repeats the
# group label as a "(Cont.)" row when a cut lands inside a group, so the six
# body rows come out as four pages of two rather than three.
pages <- as_rtftables(df, split = "group_force", max_rows = 2)
vapply(pages, function(p) nrow(p$data), integer(1)) # 2 2 2 2
#> [1] 2 2 2 2
# Blank separator rows: after row 3 and after the last row.
pages <- as_rtftables(df, blank_rows = c(3, -1))
pages[[1]]$blank_rows
#> [1] 3
if (requireNamespace("gtsummary", quietly = TRUE)) {
tbl <- gtsummary::trial |>
gtsummary::tbl_summary(by = trt) |>
as_rtftables() # list of rtftable pages
doc <- rtf_document() |>
rtf_tables(tbl) # titles / footnotes flow through
generate_rtfreport(doc, tempfile(fileext = ".rtf"), overwrite = TRUE)
}