diff --git a/main/coverage-report/index.html b/main/coverage-report/index.html index 5ba60dbe2..d5b875952 100644 --- a/main/coverage-report/index.html +++ b/main/coverage-report/index.html @@ -95,7 +95,7 @@ font-size: 11px; }
function(x) length(collect_leaves(x, TRUE, TRUE))
ncol(col_info(x))
if (!no_colinfo(x)) {
ncol(col_info(x))
length(col_exprs(x))
function(x) c(nrow(x), ncol(x))
setGeneric("tree_children", function(x) standardGeneric("tree_children"))
function(x) x@children
setGeneric("content_table", function(obj) standardGeneric("content_table"))
function(obj) obj@content
function(obj) obj@name
setMethod("obj_label", "TableRow", function(obj) obj@label)
setGeneric("tt_labelrow", function(obj) standardGeneric("tt_labelrow"))
function(obj) obj@labelrow
setGeneric("labelrow_visible", function(obj) standardGeneric("labelrow_visible"))
labelrow_visible(tt_labelrow(obj))
function(obj) obj@visible
setGeneric("row_cells", function(obj) standardGeneric("row_cells"))
setMethod("row_cells", "TableRow", function(obj) obj@leaf_value)
setMethod("obj_format", "VTableNodeInfo", function(obj) obj@format)
setMethod("obj_format", "CellValue", function(obj) attr(obj, "format", exact = TRUE))
setMethod("obj_na_str", "VTableNodeInfo", function(obj) obj@na_str)
standardGeneric("collect_leaves")
ret <- c(
if (add.labrows && labelrow_visible(tt)) {
tt_labelrow(tt)
if (incl.cont) {
tree_children(content_table(tt))
lapply(tree_children(tt),
collect_leaves,
incl.cont = incl.cont, add.labrows = add.labrows
unlist(ret, recursive = TRUE)
ret <- tree_children(tt)
if (add.labrows && labelrow_visible(tt)) {
ret <- c(tt_labelrow(tt), ret)
ret
setGeneric("row_cspans", function(obj) standardGeneric("row_cspans"))
setMethod("row_cspans", "TableRow", function(obj) obj@colspans)
setGeneric("indent_mod", function(obj) standardGeneric("indent_mod"))
function(obj) obj@indent_modifier
setGeneric("rawvalues", function(obj) standardGeneric("rawvalues"))
setMethod("rawvalues", "CellValue", function(obj) obj[[1]])
setGeneric("col_info", function(obj) standardGeneric("col_info"))
function(obj) obj@col_info
setGeneric("col_exprs", function(obj, df = NULL) standardGeneric("col_exprs"))
if (!is.null(df)) {
obj@subset_exprs
setGeneric("no_colinfo", function(obj) standardGeneric("no_colinfo"))
function(obj) no_colinfo(col_info(obj))
function(obj) length(obj@subset_exprs) == 0
setGeneric("row_footnotes", function(obj) standardGeneric("row_footnotes"))
function(obj) obj@row_footnotes
setGeneric("cell_footnotes", function(obj) standardGeneric("cell_footnotes"))
function(obj) attr(obj, "footnotes", exact = TRUE) %||% list()
ret <- lapply(row_cells(obj), cell_footnotes)
if (length(ret) != ncol(obj)) {
ret
setGeneric("ref_index", function(obj) standardGeneric("ref_index"))
function(obj) obj@index
setGeneric("ref_symbol", function(obj) standardGeneric("ref_symbol"))
function(obj) obj@symbol
setGeneric("ref_msg", function(obj) standardGeneric("ref_msg"))
function(obj) obj@value
setGeneric("trailing_section_div", function(obj) standardGeneric("trailing_section_div"))
setMethod("trailing_section_div", "VTableTree", function(obj) obj@trailing_section_div)
setMethod("trailing_section_div", "LabelRow", function(obj) obj@trailing_section_div)
setMethod("trailing_section_div", "TableRow", function(obj) obj@trailing_section_div)
setGeneric("trailing_section_div<-", function(obj, value) standardGeneric("trailing_section_div<-"))
obj@trailing_section_div <- value
obj
fns <- sum(unlist(lapply(row_footnotes(x), nlines, max_width = max_width, fontspec = fontspec))) +
sum(unlist(lapply(cell_footnotes(x), nlines, max_width = max_width, fontspec = fontspec)))
fcells <- as.vector(get_formatted_cells(x))
spans <- row_cspans(x)
have_cw <- !is.null(colwidths)
if (any(spans > 1)) {
rowext <- max(
unlist(
mapply(
function(s, w) {
nlines(strsplit(s, "\n", fixed = TRUE), max_width = w, fontspec = fontspec)
s = c(obj_label(x), fcells),
w = (colwidths %||% max_width) %||% vector("list", length(c(obj_label(x), fcells))),
SIMPLIFY = FALSE
rowext + fns
if (labelrow_visible(x)) {
nlines(strsplit(obj_label(x), "\n", fixed = TRUE)[[1]], max_width = colwidths[1], fontspec = fontspec) +
sum(unlist(lapply(row_footnotes(x), nlines, max_width = max_width, fontspec = fontspec)))
nlines(format_fnote_note(x), colwidths = colwidths, max_width = max_width, fontspec = fontspec)
indent <- indent + indent_mod(tt)
orig_rownum <- rownum # nolint
if (incontent) {
path <- c(path, "@content")
} else if (length(path) > 0 || nzchar(obj_name(tt))) { ## don't add "" for root
path <- c(path, obj_name(tt))
ret <- list()
if (!visible_only) {
if (labelrow_visible(tt)) {
lr <- tt_labelrow(tt)
newdf <- make_row_df(lr,
colwidths = colwidths,
visible_only = visible_only,
rownum = rownum,
indent = indent,
path = path,
incontent = TRUE,
repr_ext = repr_ext,
repr_inds = repr_inds,
max_width = max_width,
fontspec = fontspec
rownum <- max(newdf$abs_rownumber, na.rm = TRUE)
ret <- c(
ret,
list(newdf)
repr_ext <- repr_ext + 1L
repr_inds <- c(repr_inds, rownum)
indent <- indent + 1L
if (NROW(content_table(tt)) > 0) {
ct_tt <- content_table(tt)
cind <- indent + indent_mod(ct_tt)
trailing_section_div(ct_tt) <- trailing_section_div(tt_labelrow(tt))
contdf <- make_row_df(ct_tt,
colwidths = colwidths,
visible_only = visible_only,
rownum = rownum,
indent = cind,
path = path,
incontent = TRUE,
repr_ext = repr_ext,
repr_inds = repr_inds,
max_width = max_width,
fontspec = fontspec
crnums <- contdf$abs_rownumber
crnums <- crnums[!is.na(crnums)]
newrownum <- max(crnums, na.rm = TRUE)
if (is.finite(newrownum)) {
rownum <- newrownum
repr_ext <- repr_ext + length(crnums)
repr_inds <- c(repr_inds, crnums)
ret <- c(ret, list(contdf))
indent <- cind + 1
allkids <- tree_children(tt)
newnsibs <- length(allkids)
for (i in seq_along(allkids)) {
kid <- allkids[[i]]
kiddfs <- make_row_df(kid,
colwidths = colwidths,
visible_only = visible_only,
rownum = force(rownum),
indent = indent, ## + 1,
path = path,
incontent = incontent,
repr_ext = repr_ext,
repr_inds = repr_inds,
nsibs = newnsibs,
sibpos = i,
max_width = max_width,
fontspec = fontspec
rownum <- max(rownum + 1L, kiddfs$abs_rownumber, na.rm = TRUE)
ret <- c(ret, list(kiddfs))
ret <- do.call(rbind, ret)
if (!is.na(trailing_section_div(tt))) {
ret
indent <- indent + indent_mod(tt)
rownum <- rownum + 1
rrefs <- row_footnotes(tt)
crefs <- cell_footnotes(tt)
reflines <- sum(
sapply(
c(rrefs, crefs),
nlines,
colwidths = colwidths,
max_width = max_width,
fontspec = fontspec,
col_gap = col_gap
) ## col_gap not strictly necessary as these aren't rows, but why not
ret <- pagdfrow(
row = tt,
rnum = rownum,
colwidths = colwidths,
sibpos = sibpos,
nsibs = nsibs,
pth = c(path, unname(obj_name(tt))),
repext = repr_ext,
repind = repr_inds,
indent = indent,
extent = nlines(tt, colwidths = colwidths, max_width = max_width, fontspec = fontspec, col_gap = col_gap),
nrowrefs = length(rrefs),
ncellrefs = length(unlist(crefs)),
nreflines = reflines,
trailing_sep = trailing_section_div(tt),
fontspec = fontspec
ret
rownum <- rownum + 1
indent <- indent + indent_mod(tt)
ret <- pagdfrow(tt,
extent = nlines(tt, colwidths = colwidths, max_width = max_width, fontspec = fontspec, col_gap = col_gap),
rnum = rownum,
colwidths = colwidths,
sibpos = sibpos,
nsibs = nsibs,
pth = path,
repext = repr_ext,
repind = repr_inds,
indent = indent,
nrowrefs = length(row_footnotes(tt)),
ncellrefs = 0L,
nreflines = sum(vapply(row_footnotes(tt), nlines, NA_integer_,
colwidths = colwidths,
max_width = max_width,
fontspec = fontspec,
col_gap = col_gap
trailing_sep = trailing_section_div(tt),
fontspec = fontspec
if (!labelrow_visible(tt)) {
ret
#' page size.+
#' page size. Defaults to `section_properties_default()`.
#'+
#' @param ... (`any`)\cr additional arguments passed to [tt_to_flextable()].
#' @note `export_as_docx()` has few customization options available. If you require specific formats and details,+
#'
#' we suggest that you use [tt_to_flextable()] prior to `export_as_docx`. Only the `title_as_header` and+
#' @note `export_as_docx()` has few customization options available. If you require specific formats and details,
#' `footer_as_text` parameters must be re-specified if the table is changed first using [tt_to_flextable()].+
#' we suggest that you use [tt_to_flextable()] prior to `export_as_docx`. Only the `title_as_header` and
#'+
#' `footer_as_text` parameters must be re-specified if the table is changed first using [tt_to_flextable()].
#' @seealso [tt_to_flextable()]+
#'
#'+
#' @seealso [tt_to_flextable()]
#' @examples+
#'
#' lyt <- basic_table() %>%+
#' @examples
#' split_cols_by("ARM") %>%+
#' lyt <- basic_table() %>%
#' analyze(c("AGE", "BMRKR2", "COUNTRY"))+
#' split_cols_by("ARM") %>%
#'+
#' analyze(c("AGE", "BMRKR2", "COUNTRY"))
#' tbl <- build_table(lyt, ex_adsl)+
#'
#'+
#' tbl <- build_table(lyt, ex_adsl)
#' # See how section_properties_portrait function is built for custom+
#'
#' \dontrun{+
#' # See how section_properties_portrait function is built for custom
#' tf <- tempfile(fileext = ".docx")+
#' \dontrun{
#' export_as_docx(tbl, file = tf, section_properties = section_properties_portrait())+
#' tf <- tempfile(fileext = ".docx")
#' }+
#' export_as_docx(tbl,
#'+
#' file = tf,
#' @export+
#' section_properties = section_properties_default(orientation = "landscape")
export_as_docx <- function(tt,+
#' )
file,+
#' }
doc_metadata = NULL,+
#'
titles_as_header = FALSE,+
#' @export
footers_as_text = TRUE,+
export_as_docx <- function(tt,
template_file = NULL,+
file,
section_properties = NULL) {+
doc_metadata = NULL,
titles_as_header = FALSE,+
footers_as_text = TRUE,+
template_file = NULL,+
section_properties = section_properties_default(),+
...) {+
# Checks
check_required_packages(c("flextable", "officer"))
if (inherits(tt, "VTableTree")) {
flex_tbl <- tt_to_flextable(tt,
titles_as_header = titles_as_header,
footers_as_text = footers_as_text+
footers_as_text = footers_as_text,
...+
)
if (isFALSE(titles_as_header) || isTRUE(footers_as_text)) {
# Ugly but I could not find a getter for font.size
font_sz <- flex_tbl$header$styles$text$font.size$data[1, 1]
font_sz_footer <- flex_tbl$header$styles$text$font.size$data[1, 1] - 1
font_fam <- flex_tbl$header$styles$text$font.family$data[1, 1]
# Set the test as the tt
fpt <- officer::fp_text(font.family = font_fam, font.size = font_sz)
fpt_footer <- officer::fp_text(font.family = font_fam, font.size = font_sz_footer)
}
} else {
flex_tbl <- tt
}
if (!is.null(template_file) && !file.exists(template_file)) {
template_file <- NULL
}
# Create a new empty Word document
if (!is.null(template_file)) {
doc <- officer::read_docx(template_file)
} else {
doc <- officer::read_docx()
}
if (!is.null(section_properties)) {
doc <- officer::body_set_default_section(doc, section_properties)
}
# Extract title
if (isFALSE(titles_as_header) && inherits(tt, "VTableTree")) {
ts_tbl <- all_titles(tt)
if (length(ts_tbl) > 0) {
doc <- add_text_par(doc, ts_tbl, fpt)
}
}
# Add the table to the document
doc <- flextable::body_add_flextable(doc, flex_tbl, align = "left")
# add footers as paragraphs
if (isTRUE(footers_as_text) && inherits(tt, "VTableTree")) {
# Adding referantial footer line separator if present+
# Adding referential footer line separator if present
# (this is usually done differently, i.e. inside footnotes)
matform <- matrix_form(tt, indent_rownames = TRUE)
if (length(matform$ref_footnotes) > 0) {
doc <- add_text_par(doc, matform$ref_footnotes, fpt_footer)
}
# Footer lines
if (length(all_footers(tt)) > 0) {
doc <- add_text_par(doc, all_footers(tt), fpt_footer)
}
}
if (!is.null(doc_metadata)) {
# Checks for values rely on officer function
doc <- do.call(officer::set_doc_properties, c(list("x" = doc), doc_metadata))
}
# Save the Word document to a file
print(doc, target = file)
}
# Shorthand to add text paragraph
add_text_par <- function(doc, chr_v, text_format) {
for (ii in seq_along(chr_v)) {
cur_fp <- officer::fpar(officer::ftext(chr_v[ii], prop = text_format))
doc <- officer::body_add_fpar(doc, cur_fp)
}
doc
}
#' @describeIn export_as_docx Helper function that defines standard portrait properties for tables.
#' @param page_size (`character(1)`) page size. Can be `"letter"` or `"A4"`. Defaults to `"letter"`.+
#' @param orientation (`character(1)`) page orientation. Can be `"portrait"` or `"landscape"`. Defaults to+
#' `"portrait"`.+
#'+
#' @export
section_properties_default <- function(page_size = c("letter", "A4"),+
section_properties_portrait <- function() {+
orientation = c("portrait", "landscape")) {
officer::prop_section(+
page_size <- page_size[1]
page_size = officer::page_size(+
orientation <- orientation[1]
orient = "portrait",+
checkmate::assert_choice(
width = 8.5, height = 11+
page_size,+
eval(formals(section_properties_default)$page_size)
),+
)
type = "continuous",+
checkmate::assert_choice(
page_margins = margins_potrait()+
orientation,
)+
eval(formals(section_properties_default)$orientation)
}+
)
if (page_size == "letter") {+
page_size <- officer::page_size(+
#' @describeIn export_as_docx Helper function that defines standard landscape properties for tables.+
orient = orientation,+
width = 8.5, height = 11
#' @export+
)
section_properties_landscape <- function() {+
} else { # A4
officer::prop_section(+
page_size <- officer::page_size(
page_size = officer::page_size(+
orient = orientation,
orient = "landscape",+
width = 8.27, height = 11.69
width = 8.5, height = 11+
)
),+
}+
# Final output
officer::prop_section(+
page_size = page_size,+
type = "continuous",
page_margins = margins_landscape()+
page_margins = margins_potrait()
)
}
#' @describeIn export_as_docx Helper function that defines standard portrait margins for tables.
#' @export
margins_potrait <- function() {
officer::page_mar(bottom = 0.98, top = 0.95, left = 1.5, right = 1, gutter = 0)
}
#' @describeIn export_as_docx Helper function that defines standard landscape margins for tables.
#' @export
margins_landscape <- function() {
officer::page_mar(bottom = 1, top = 1.5, left = 0.98, right = 0.95, gutter = 0)
}
#' Create a `flextable` from an `rtables` table
#'
#' Principally used for export ([export_as_docx()]), this function produces a `flextable`
#' from an `rtables` table. If `theme = NULL`, `rtables`-like style will be used. Otherwise,
#' [theme_docx_default()] will produce a `.docx`-friendly table.
#'
#' @inheritParams gen_args
#' @inheritParams paginate_table
#' @param theme (`function` or `NULL`)\cr A theme function that is designed internally as a function of a `flextable`
#' object to change its layout and style. If `NULL`, it will produce a table similar to `rtables` default. Defaults
#' to `theme_docx_default(tt)`.+
#' to `theme_docx_default()`. See details for more information.
#' @param border (`officer` border object)\cr defaults to `officer::fp_border(width = 0.5)`.
#' @param indent_size (`integer(1)`)\cr if `NULL`, the default indent size of the table (see [formatters::matrix_form()]+
#' @param indent_size (`numeric(1)`)\cr if `NULL`, the default indent size of the table (see [formatters::matrix_form()]
#' `indent_size`) is used. To work with `docx`, any size is multiplied by 2 mm (5.67 pt) by default.+
#' `indent_size`, default is 2) is used. To work with `docx`, any size is multiplied by 1 mm (2.83 pt) by default.
#' @param titles_as_header (`flag`)\cr defaults to `TRUE` for [tt_to_flextable()], so the table is self-contained
#' as it makes additional header rows for [formatters::main_title()] string and [formatters::subtitles()] character
#' vector (one per element). `FALSE` is suggested for [export_as_docx()]. This adds titles and subtitles as a text
#' paragraph above the table. The same style is applied.
#' @param footers_as_text (`flag`)\cr defaults to `FALSE` for [tt_to_flextable()], so the table is self-contained with
#' the `flextable` definition of footnotes. `TRUE` is used for [export_as_docx()] to add the footers as a new
#' paragraph after the table. The same style is applied, but with a smaller font.
#' @param counts_in_newline (`flag`)\cr defaults to `FALSE`. In `rtables` text printing ([formatters::toString()]),
#' the column counts, i.e. `(N=xx)`, are always on a new line. For `docx` exports it could be necessary to print it
#' on the same line.
#' @param paginate (`flag`)\cr when exporting `.docx` documents using `export_as_docx`, we suggest relying on the
#' Microsoft Word pagination system. If `TRUE`, this option splits `tt` into different "pages" as multiple
#' `flextables`. Cooperation between the two mechanisms is not guaranteed. Defaults to `FALSE`.
#' @param total_width (`numeric(1)`)\cr total width (in inches) for the resulting flextable(s). Defaults to 10.
#'
#' @return A `flextable` object.
#'
#' @details+
#' It is possible to use some hidden values for building your own theme. In particular, `tt_to_flextable`+
#' sends in the following variables `tbl_ncol_body = NCOL(tt)` and `tbl_row_class = make_row_df(tt)$node_class`.+
#' These are ignored if not used in the theme. See `theme_docx_default` for an example on own to retrieve+
#' these values and how to use them.+
#'+
#'+
#' @seealso [export_as_docx()]
#'
#' @examples
#' analysisfun <- function(x, ...) {
#' in_rows(
#' row1 = 5,
#' row2 = c(1, 2),
#' .row_footnotes = list(row1 = "row 1 - row footnote"),
#' .cell_footnotes = list(row2 = "row 2 - cell footnote")
#' )
#' }
#'
#' lyt <- basic_table(
#' title = "Title says Whaaaat", subtitles = "Oh, ok.",
#' main_footer = "ha HA! Footer!"
#' ) %>%
#' split_cols_by("ARM") %>%
#' analyze("AGE", afun = analysisfun)
#'
#' tbl <- build_table(lyt, ex_adsl)
#' # rtables style
#' tt_to_flextable(tbl, theme = NULL)
#'
#' tt_to_flextable(tbl, theme = theme_docx_default(tbl, font_size = 7))+
#' tt_to_flextable(tbl, theme = theme_docx_default(font_size = 6))
#'
#' @export
tt_to_flextable <- function(tt,
theme = theme_docx_default(tt),+
theme = theme_docx_default(),
border = flextable::fp_border_default(width = 0.5),
indent_size = NULL,
titles_as_header = TRUE,
footers_as_text = FALSE,
counts_in_newline = FALSE,
paginate = FALSE,
lpp = NULL,
cpp = NULL,
...,
colwidths = propose_column_widths(matrix_form(tt, indent_rownames = TRUE)),
tf_wrap = !is.null(cpp),
max_width = cpp,
total_width = 10) {
check_required_packages("flextable")
if (!inherits(tt, "VTableTree")) {
stop("Input table is not an rtables' object.")
}
checkmate::assert_flag(titles_as_header)
checkmate::assert_flag(footers_as_text)
checkmate::assert_flag(counts_in_newline)
left_right_fixed_margins <- word_mm_to_pt(1.9)+
## if we're paginating, just call -> pagination happens also afterwards if needed
if (paginate) {
if (is.null(lpp)) {
stop("lpp must be specified when calling tt_to_flextable with paginate=TRUE")
}
tabs <- paginate_table(tt, lpp = lpp, cpp = cpp, tf_wrap = tf_wrap, max_width = max_width, ...)
cinds <- lapply(tabs, function(tb) c(1, .figure_out_colinds(tb, tt) + 1L))
return(mapply(tt_to_flextable,
tt = tabs, colwidths = cinds,
MoreArgs = list(paginate = FALSE, total_width = total_width),
SIMPLIFY = FALSE
))
}
# Calculate the needed colwidths
final_cwidths <- total_width * colwidths / sum(colwidths) # xxx to fix
# xxx FIXME missing transformer from character based widths to mm or pt
# Extract relevant information
matform <- matrix_form(tt, indent_rownames = TRUE)+
matform <- matrix_form(tt, indent_rownames = FALSE)
body <- mf_strings(matform) # Contains header
spans <- mf_spans(matform) # Contains header
mpf_aligns <- mf_aligns(matform) # Contains header
hnum <- mf_nlheader(matform) # Number of lines for the header
rdf <- make_row_df(tt) # Row-wise info
# decimal alignment pre-proc
if (any(grepl("dec", mpf_aligns))) {
body <- decimal_align(body, mpf_aligns)
# Coercion for flextable
mpf_aligns[mpf_aligns == "decimal"] <- "center"
mpf_aligns[mpf_aligns == "dec_left"] <- "left"
mpf_aligns[mpf_aligns == "dec_right"] <- "right"
}
# Fundamental content of the table
content <- as.data.frame(body[-seq_len(hnum), , drop = FALSE])
flx <- flextable::qflextable(content) %>%
# Default rtables if no footnotes
remove_hborder(part = "body", w = "bottom")
# Header addition -> NB: here we have a problem with (N=xx)
hdr <- body[seq_len(hnum), , drop = FALSE]
# XXX NOT NECESSARY change of (N=xx) which is by default on a new line but we do not
# want this in docx, and it depends on the size of the table, it is not another
# row with different columns -> All of this should be fixed at source (in matrix_form)
# See .tbl_header_mat for this change
if (hnum > 1) { # otherwise nothing to do
det_nclab <- apply(hdr, 2, grepl, pattern = "\\(N=[0-9]+\\)$")
has_nclab <- apply(det_nclab, 1, any)
whsnc <- which(has_nclab) # which rows have it -> more than one is not supported
if (isFALSE(counts_in_newline) && any(has_nclab) && length(whsnc) == 1L) {
what_is_nclab <- det_nclab[whsnc, ]
# condition for popping the interested row by merging the upper one
hdr[whsnc, what_is_nclab] <- paste(hdr[whsnc - 1, what_is_nclab],
hdr[whsnc, what_is_nclab],
sep = " "
)
hdr[whsnc - 1, what_is_nclab] <- ""
# We can remove the row if they are all ""
row_to_pop <- whsnc - 1
if (all(!nzchar(hdr[row_to_pop, ]))) {
hdr <- hdr[-row_to_pop, , drop = FALSE]
spans <- spans[-row_to_pop, , drop = FALSE]
body <- body[-row_to_pop, , drop = FALSE]
mpf_aligns <- mpf_aligns[-row_to_pop, , drop = FALSE]
hnum <- hnum - 1
}
}
}
flx <- flx %>%
flextable::set_header_labels( # Needed bc headers must be unique
values = setNames(
as.vector(hdr[hnum, , drop = TRUE]),
names(content)
)
)
# If there are more rows
if (hnum > 1) {
for (i in seq(hnum - 1, 1)) {
sel <- spans_to_viscell(spans[i, ])
flx <- flextable::add_header_row(
flx,
top = TRUE,
values = as.vector(hdr[i, sel]),
colwidths = as.integer(spans[i, sel]) # xxx to fix
)
}
}
# Re-set the number of row count+
nr_body <- flextable::nrow_part(flx, part = "body")+
nr_header <- flextable::nrow_part(flx, part = "header")+
# Polish the inner horizontal borders from the header
flx <- flx %>%
remove_hborder(part = "header", w = "all") %>%
add_hborder("header", ii = c(0, hnum), border = border)
# ALIGNS
flx <- flx %>%
apply_alignments(mpf_aligns[seq_len(hnum), , drop = FALSE], "header") %>%
apply_alignments(mpf_aligns[-seq_len(hnum), , drop = FALSE], "body")
# Rownames indentation
checkmate::check_int(indent_size, null.ok = TRUE)+
checkmate::check_number(indent_size, null.ok = TRUE)
if (is.null(indent_size)) {
# Default indent_size in {rtables} is 2 characters+
indent_size <- matform$indent_size * word_mm_to_pt(2) # default is 2mm (5.7pt)+
indent_size <- matform$indent_size * word_mm_to_pt(1) # default is 2mm (5.7pt)
} else {+
indent_size <- indent_size * word_mm_to_pt(1)+
}
# rdf contains information about indentation+
for (i in seq_len(NROW(tt))) {+
for (i in seq_len(nr_body)) {
flx <- flextable::padding(flx,
i = i, j = 1,
padding.left = indent_size * rdf$indent[[i]] + word_mm_to_pt(0.1), # 0.1 mmm in pt+
padding.left = indent_size * rdf$indent[[i]] + left_right_fixed_margins, # margins
padding.right = left_right_fixed_margins, # 0.19 mmm in pt (so not to touch the border)+
padding.right = word_mm_to_pt(0.1) # 0.1 mmm in pt (so not to touch the border)+
part = "body"
)
}
# Adding referantial footer line separator if present+
# TOPLEFT+
# Principally used for topleft indentation, this is a bit of a hack xxx
if (length(matform$ref_footnotes) > 0 && isFALSE(footers_as_text)) {+
for (i in seq_len(nr_header)) {
flx <- flextable::add_footer_lines(flx, values = matform$ref_footnotes) %>%+
leading_spaces_count <- nchar(hdr[i, 1]) - nchar(stringi::stri_replace(hdr[i, 1], regex = "^ +", ""))
add_hborder(part = "body", ii = nrow(tt), border = border)+
header_indent_size <- leading_spaces_count * word_mm_to_pt(1)
}+
flx <- flextable::padding(flx,
i = i, j = 1,+
padding.left = header_indent_size + left_right_fixed_margins, # margins+
padding.right = left_right_fixed_margins, # 0.19 mmm in pt (so not to touch the border)+
part = "header"
)+
}+
# topleft styling (-> bottom aligned) xxx merge_at() could merge these, but let's see+
flx <- flextable::valign(flx, j = 1, valign = "bottom", part = "header")+
# Adding referantial footer line separator if present+
if (length(matform$ref_footnotes) > 0 && isFALSE(footers_as_text)) {+
flx <- flextable::add_footer_lines(flx, values = matform$ref_footnotes) %>%+
add_hborder(part = "body", ii = nrow(tt), border = border)+
}+
# Footer lines
if (length(all_footers(tt)) > 0 && isFALSE(footers_as_text)) {
flx <- flextable::add_footer_lines(flx, values = all_footers(tt))
}
flx <- flextable::width(flx, width = final_cwidths) # xxx to fix
if (!is.null(theme)) {
flx <- theme(flx)+
flx <- theme(+
flx,+
tbl_ncol_body = flextable::ncol_keys(flx), # NCOL(tt) + 1, # +1 for rownames+
tbl_row_class = make_row_df(tt)$node_class # These are ignored if not in the theme
)+
}
# Title lines (after theme for problems with lines)
if (titles_as_header && length(all_titles(tt)) > 0 && any(nzchar(all_titles(tt)))) {
real_titles <- all_titles(tt)
real_titles <- real_titles[nzchar(real_titles)]
flx <- flextable::add_header_lines(flx, values = real_titles, top = TRUE) %>%
# Remove the added borders
remove_hborder(part = "header", w = c("inner", "top")) %>%
# Re-add the separator between titles and real headers
add_hborder(
part = "header", ii = length(real_titles),
border = border
) %>%
# Remove vertical borders added by theme eventually
remove_vborder(part = "header", ii = seq_along(real_titles))
}
# These final formatting need to work with colwidths
flx <- flextable::set_table_properties(flx, layout = "autofit", align = "left") # xxx to fix
# NB: autofit or fixed may be switched if widths are correctly staying in the page
flx <- flextable::fix_border_issues(flx) # Fixes some rendering gaps in borders
flx
}
#' @describeIn tt_to_flextable Main theme function for [export_as_docx()]
#'
#' @inheritParams export_as_docx+
#' @param font (`string`)\cr defaults to `"Arial"`. If the font is not available, `flextable` default is used.
#' @param font (`string`)\cr defaults to `"Arial"`. If the font is not available, `flextable` default is used.+
#' @param font_size (`integer(1)`)\cr font size. Defaults to 9.
#' @param font_size (`integer(1)`)\cr font size. Defaults to 9.+
#' @param cell_margins (`numeric(1)` or `numeric(4)`)\cr a numeric or a vector of four numbers indicating
#' `c("left", "right", "top", "bottom")`. It defaults to 0 for top and bottom, and to 0.19 `mm` in word `pt`+
#' for left and right.+
#' @param bold (`character`)\cr parts of the table text that should be in bold. Can be any combination of
#' `c("header", "content_rows", "label_rows")`. The first one renders all column names bold (not `topleft` content).
#' The second and third option use [formatters::make_row_df()] to render content or/and label rows as bold.
#' @param bold_manual (named `list` or `NULL`)\cr list of index lists. See example for needed structure. Accepted
#' groupings/names are `c("header", "body")`.
#' @param border (`flextable::fp_border()`)\cr border style. Defaults to `flextable::fp_border_default(width = 0.5)`.+
#'
#' @seealso [export_as_docx()]
#'
#' @examples
#' # Custom theme
#' special_bold <- list(
#' "header" = list("i" = 1, "j" = c(1, 3)),
#' "body" = list("i" = c(1, 2), "j" = 1)
#' )
#' custom_theme <- theme_docx_default(tbl,+
#' custom_theme <- theme_docx_default(
#' font_size = 10,
#' font = "Brush Script MT",
#' border = flextable::fp_border_default(color = "pink", width = 2),
#' bold = NULL,
#' bold_manual = special_bold
#' )
#' tt_to_flextable(tbl,
#' border = flextable::fp_border_default(color = "pink", width = 2),
#' theme = custom_theme
#' )
#'
#' @export
theme_docx_default <- function(tt = NULL, # Option for more complicated stuff+
theme_docx_default <- function(font = "Arial",
font = "Arial",+
font_size = 9,
font_size = 9,+
cell_margins = c(
word_mm_to_pt(1.9),+
word_mm_to_pt(1.9),+
0,+
0+
), # Default in docx+
bold = c("header", "content_rows", "label_rows"),
bold_manual = NULL,
border = flextable::fp_border_default(width = 0.5)) {
function(flx) {+
function(flx, ...) {
check_required_packages("flextable")
if (!inherits(flx, "flextable")) {
stop(sprintf(
"Function `%s` supports only flextable objects.",
"theme_box()"
))
}
checkmate::assert_int(font_size, lower = 6, upper = 12)+
checkmate::assert_string(font)+
checkmate::assert_subset(bold,+
eval(formals(theme_docx_default)$bold),+
empty.ok = TRUE+
)+
if (!is.null(tt) && !inherits(tt, "VTableTree")) {+
if (length(cell_margins) == 1) {
stop("Input table is not an rtables' object.")+
cell_margins <- rep(cell_margins, 4)
}
checkmate::assert_int(font_size, lower = 1)+
checkmate::assert_numeric(cell_margins, lower = 0, len = 4)+
# Setting values coming from ...
checkmate::assert_string(font)+
args <- list(...)
checkmate::assert_subset(bold,+
tbl_row_class <- args$tbl_row_class # This is internal info
eval(formals(theme_docx_default)$bold),+
tbl_ncol_body <- args$tbl_ncol_body # This is internal info
empty.ok = TRUE+
if (is.null(tbl_ncol_body)) {+
tbl_ncol_body <- flextable::ncol_keys(flx) # tbl_ncol_body respects if rownames = FALSE
)+
}
# Font setting
flx <- flextable::fontsize(flx, size = font_size, part = "all") %>%
flextable::fontsize(size = font_size - 1, part = "footer") %>%
flextable::font(fontname = font, part = "all")
# Vertical borders
flx <- flx %>%
flextable::border_outer(part = "body", border = border) %>%
flextable::border_outer(part = "header", border = border)+
flextable::border_outer(part = "header", border = border) %>%+
flextable::border_inner(part = "header", border = border) # xxx
# Vertical alignment -> all top for now, we will set it for the future+
# Vertical alignment -> all top for now
flx <- flx %>%
flextable::valign(j = 2:(NCOL(tt) + 1), valign = "top", part = "body") %>%+
flextable::valign(j = seq(2, tbl_ncol_body), valign = "top", part = "body") %>%
flextable::valign(j = 1, valign = "top", part = "body") %>%
flextable::valign(j = 2:(NCOL(tt) + 1), valign = "top", part = "header")+
flextable::valign(j = seq(2, tbl_ncol_body), valign = "top", part = "header")
# Vertical padding/spaces - rownames+
flx <- flx %>% # summary/data rows and cells+
flextable::padding(padding.top = cell_margins[3], padding.bottom = cell_margins[4], part = "body")+
if (any(tbl_row_class == "LabelRow")) { # label rows - 3pt top+
flx <- flextable::padding(flx,+
j = 1, i = which(tbl_row_class == "LabelRow"),+
padding.top = 3 + cell_margins[3], padding.bottom = cell_margins[4], part = "body"+
)+
}+
if (any(tbl_row_class == "ContentRow")) { # content rows - 1pt top+
flx <- flextable::padding(flx,+
# j = 1, # removed because I suppose we want alignment with body+
i = which(tbl_row_class == "ContentRow"),+
padding.top = 1 + cell_margins[3], padding.bottom = cell_margins[4], part = "body"+
)+
}+
# Horizontal padding all table margin 0.19 mm+
flx <- flextable::padding(flx,+
j = seq(2, tbl_ncol_body),+
padding.left = cell_margins[1],+
padding.right = cell_margins[2]+
)+
# Vertical padding/spaces - header (3pt after)+
flx <- flx %>%+
flextable::padding(+
j = seq(2, tbl_ncol_body),+
padding.top = cell_margins[3],+
padding.bottom = cell_margins[4],+
part = "header"+
)+
# single line spacing (for safety) -> space = 1+
flx <- flextable::line_spacing(flx, space = 1, part = "all")+
# Bold settings
if (any(bold == "header")) {
flx <- flextable::bold(flx, j = 2:(NCOL(tt) + 1), part = "header") # Done with theme+
flx <- flextable::bold(flx, j = seq(2, tbl_ncol_body), part = "header") # Done with theme
}
# Content rows are effectively our labels in row names
if (any(bold == "content_rows")) {
if (is.null(tt)) stop('bold = "content_rows" needs the original rtables object (tt).')+
if (is.null(tbl_row_class)) {
rdf <- make_row_df(tt)+
stop('bold = "content_rows" needs tbl_row_class = make_row_df(tt).')
which_body <- which(rdf$node_class == "ContentRow")+
}
flx <- flextable::bold(flx, j = 1, i = which_body, part = "body")+
flx <- flextable::bold(flx, j = 1, i = which(tbl_row_class == "ContentRow"), part = "body")
}
if (any(bold == "label_rows")) {
if (is.null(tbl_row_class)) {+
if (is.null(tt)) stop('bold = "content_rows" needs the original rtables object (tt).')+
stop('bold = "content_rows" needs tbl_row_class = make_row_df(tt).')+
}
rdf <- make_row_df(tt)+
flx <- flextable::bold(flx, j = 1, i = which(tbl_row_class == "LabelRow"), part = "body")+
}+
# topleft information is also bold if content or label rows are bold
which_body <- which(rdf$node_class == "LabelRow")+
if (any(bold %in% c("content_rows", "label_rows"))) {
flx <- flextable::bold(flx, j = 1, i = which_body, part = "body")+
flx <- flextable::bold(flx, j = 1, part = "header")
}
# If you want specific cells to be bold
if (!is.null(bold_manual)) {
checkmate::assert_list(bold_manual)
valid_sections <- c("header", "body") # Only valid values
checkmate::assert_subset(names(bold_manual), valid_sections)
for (bi in seq_along(bold_manual)) {
bld_tmp <- bold_manual[[bi]]
checkmate::assert_list(bld_tmp)
if (!all(c("i", "j") %in% names(bld_tmp)) || !all(vapply(bld_tmp, checkmate::test_integerish, logical(1)))) {
stop(
"Found an allowed section for manual bold (", names(bold_manual)[bi],
") that was not a named list with i (row) and j (col) integer vectors."
)
}
flx <- flextable::bold(flx,
i = bld_tmp$i, j = bld_tmp$j,
part = names(bold_manual)[bi]
)
}
}
# vertical padding is manual atm and respect doc std-
flx <- flx %>%+
flx
# flextable::padding(j = 2:(NCOL(tt) + 1), padding.top = , part = "body") %>% # not specified-
flextable::padding(j = 1, padding.top = 1, padding.bottom = 1, part = "body") %>%+
}
flextable::padding(j = 2:(NCOL(tt) + 1), padding.top = 0, padding.bottom = 3, part = "header")+
}
# single line spacing (for safety) -> space = 1-
flx <- flextable::line_spacing(flx, space = 1, part = "all")+
#' @describeIn tt_to_flextable Padding helper functions to transform mm to pt.
flx+
#' @param mm (`numeric(1)`)\cr the value in mm to transform to pt.
}+
#'
}+
#' @export
word_mm_to_pt <- function(mm) {
# Padding helper functions to transform mm to pt and viceversa+
mm / 0.3527777778
# # General note for word: 1pt -> 0.3527777778mm -> 0.013888888888889"+
}
word_inch_to_pt <- function(inch) { # nocov+
inch / 0.013888888888889 # nocov+
# Padding helper functions to transform mm to pt and viceversa
}+
# # General note for word: 1pt -> 0.3527777778mm -> 0.013888888888889"
word_inch_to_pt <- function(inch) { # nocov
word_mm_to_pt <- function(mm) {-
mm / 0.3527777778+
inch / 0.013888888888889 # nocov
}
# Polish horizontal borders
remove_hborder <- function(flx, part, w = c("top", "bottom", "inner")) {
# If you need to remove all of them
if (length(w) == 1 && w == "all") {
w <- eval(formals(remove_hborder)$w)
}
if (any(w == "top")) {
flx <- flextable::hline_top(flx,
border = flextable::fp_border_default(width = 0),
part = part
)
}
if (any(w == "bottom")) {
flx <- flextable::hline_bottom(flx,
border = flextable::fp_border_default(width = 0),
part = part
)
}
# Inner horizontal lines removal
if (any(w == "inner")) {
flx <- flextable::border_inner_h(
flx,
border = flextable::fp_border_default(width = 0),
part = part
)
}
flx
}
# Remove vertical borders from both sides (for titles)
remove_vborder <- function(flx, part, ii) {
flx <- flextable::border(flx,
i = ii, part = part,
border.left = flextable::fp_border_default(width = 0),
border.right = flextable::fp_border_default(width = 0)
)
}
# Add horizontal border
add_hborder <- function(flx, part, ii, border) {
if (any(ii == 0)) {
flx <- flextable::border(flx, i = 1, border.top = border, part = part)
ii <- ii[!(ii == 0)]
}
if (length(ii) > 0) {
flx <- flextable::border(flx, i = ii, border.bottom = border, part = part)
}
flx
}
apply_alignments <- function(flx, aligns_df, part) {
# List of characters you want to search for
search_chars <- unique(c(aligns_df))
# Loop through each character and find its indexes
for (char in search_chars) {
indexes <- which(aligns_df == char, arr.ind = TRUE)
tmp_inds <- as.data.frame(indexes)
flx <- flx %>%
flextable::align(
i = tmp_inds[["row"]],
j = tmp_inds[["col"]],
align = char,
part = part
)
}
flx
}@@ -126090,14 +126860,14 @@
body[, 1] <- indent_string(body[, 1], c(rep(0, nr_header), sr$indent),
incr = indent_size
formats[, 1] <- indent_string(formats[, 1], c(rep(0, nr_header), sr$indent),
incr = indent_size
} else if (NROW(sr) > 0) {
sr$indent <- rep(0, NROW(sr))
if (!is(fn, "RefFootnote")) {
ret <- ref_symbol(fn)
if (is.na(ret)) {
ret <- as.character(ref_index(fn))
ret
if (length(fn) == 0 || (is.list(fn) && all(vapply(fn, function(x) length(x) == 0, TRUE)))) {
if (is.list(fn)) {
if (is(fn, "RefFootnote")) {
paste0("{", .resolve_fn_symbol(fn), "} - ", ref_msg(fn))
setGeneric("get_formatted_cells", function(obj, shell = FALSE) standardGeneric("get_formatted_cells"))
pr_row_format <- if (is.null(obj_format(obj))) "xx" else obj_format(obj)
pr_row_na_str <- obj_na_str(obj) %||% "NA"
matrix(
unlist(Map(function(val, spn, shelli) {
stopifnot(is(spn, "integer"))
out <- format_rcell(val,
pr_row_format = pr_row_format,
pr_row_na_str = pr_row_na_str,
shell = shelli
if (!is.function(out) && is.character(out)) {
out <- paste(out, collapse = ", ")
rep(list(out), spn)
}, val = row_cells(obj), spn = row_cspans(obj), shelli = shell)),
ncol = ncol(obj)
if (length(x) > 0) {
indent <- rep_len(indent, length.out = length(x))
incr <- rep_len(incr, length.out = length(x))
indent_str <- strrep(" ", (indent > 0) * indent * incr)
if (including_newline) {
x <- unlist(mapply(function(xi, stri) {
gsub("\n", stri, xi, fixed = TRUE)
}, x, paste0("\n", indent_str)))
paste0(indent_str, x)
format <- if (missing(format)) obj_format(x) else format
if (is.null(format) && !is.null(pr_row_format)) {
format <- pr_row_format
if (is.null(obj_na_str(x)) && !is.null(pr_row_na_str)) {
na_str <- pr_row_na_str
if (shell) {
format_value(rawvalues(x),
format = format,
output = output,
na_str = na_str