Nothing
# flextable grob ----------------------------------------------------------
#' @export
#' @title Convert a flextable to a grid grob object
#' @description It uses Grid Graphics (package `grid`) to Convert a flextable
#' into a grob object with scaling and text wrapping capabilities.
#'
#' This method can be used to insert a flextable inside a `ggplot2` plot,
#' it can also be used with package 'patchwork' or 'cowplot' to combine
#' ggplots and flextables into the same graphic.
#'
#' User can vary the size of the elements according to the size of the graphic window. The text
#' behavior is controllable, user can decide to make the paragraphs (texts and images)
#' distribute themselves correctly in the available space of the cell. It is possible
#' to define resizing options, for example by using only the width, or by distributing
#' the content so that it occupies the whole graphic space. It is also possible to
#' freeze or not the size of the columns.
#'
#' It is not recommended to use this function for
#' large tables because the calculations can be long.
#'
#' Limitations: equations (see [as_equation()]) and hyperlinks (see [officer::hyperlink_ftext()])
#' will not be displayed.
#'
#' @inheritSection save_as_image caption
#' @section size:
#'
#' The size of the flextable can be known by using the method
#' \link[=dim.flextableGrob]{dim} on the grob.
#'
#' @param x A flextable object
#'
#' @param fit Determines the fitting/scaling of the grob on its parent viewport.
#' One of `auto`, `width`, `fixed`, `TRUE`, `FALSE`:
#' \itemize{
#' \item `auto` or `TRUE` (default): The grob is resized to fit in the parent viewport.
#' The table row heights and column widths are resized proportionally.
#' \item `width`: The grob is resized horizontally to fit the width
#' of the parent viewport. The column widths are resized proportionally.
#' The row heights are unaffected and the table height may be smaller or larger
#' than the height of the parent viewport.
#' \item `fixed` or `FALSE`: The grob will have fixed dimensions,
#' as determined by the column widths and the row heights.
#' }
#'
#' @param scaling Determines the scaling of the table contents.
#' One of `min`, `full`, `fixed`, `TRUE`, `FALSE`:
#' \itemize{
#' \item `min` or `TRUE` (default):
#' When the parent viewport is smaller than the necessary,
#' the various content sizes (text font size, line width and image dimensions)
#' will decrease accordingly so that the content can still fit.
#' When the parent viewport is larger than the necessary,
#' the content sizes will remain the same, they will not increase.
#' \item `full`: Same as `min`, except that the content sizes are scaled fully,
#' they will increase or decrease, according to the size of the drawing surface.
#' \item `fixed` or `FALSE`: The content sizes will not be scaled.
#' }
#'
#' @param wrapping Determines the soft wrapping (line breaking) method
#' for the table cell contents. One of `TRUE`, `FALSE`:
#' \itemize{
#' \item `TRUE`: Text content may wrap into separate lines at normal word break points
#' (such as a space or tab character between two words) or at newline characters
#' anywhere in the text content. If a word does not fit in the available cell width,
#' then the text content may wrap at any character.
#' Non-text content (such as images) is also wrapped into new lines,
#' according to the available cell width.
#' \item `FALSE`: Text content may wrap only with a newline character.
#' Non-text content is not wrapped.
#' }
#'
#' Superscript and subscript chunks do not wrap.
#' Newline and tab characters are removed from these chunk types.
#'
#' @param autowidths If `TRUE` (default) the column widths are adjusted
#' in order to fit the contents of the cells (taking into account the `wrapping` setting).
#'
#' @param just Justification of viewport layout,
#' same as `just` argument in [grid::grid.layout()].
#' When set to `NULL` (default), it is determined according to the `fit` argument.
#'
#' @param ... Reserved for extra arguments
#'
#' @return a grob (gTree) object made with package `grid`
#' @examples
#' \dontshow{
#' data.table::setDTthreads(1)
#' }
#' library(ragg)
#' library(gdtools)
#' register_liberationsans()
#'
#' set_flextable_defaults(font.family = "Liberation Sans")
#'
#' ft <- flextable(head(mtcars))
#'
#' gr <- gen_grob(ft)
#'
#' png_f_1 <- tempfile(fileext = ".png")
#' ragg::agg_png(
#' filename = png_f_1, width = 4, height = 2,
#' units = "in", res = 150)
#' plot(gr)
#' dev.off()
#'
#' png_f_2 <- tempfile(fileext = ".png")
#' # get the size
#' dims <- dim(gr)
#' dims
#' ragg::agg_png(
#' filename = png_f_2, width = dims$width + .1,
#' height = dims$height + .1, units = "in", res = 150
#' )
#' plot(gr)
#' dev.off()
#'
#'
#' if (require("ggplot2")) {
#' png_f_3 <- tempfile(fileext = ".png")
#' z <- summarizor(iris, by = "Species") |>
#' as_flextable(spread_first_col = TRUE) |>
#' color(color = "gray", part = "all")
#' gg <- ggplot(data = iris, aes(Sepal.Length, Petal.Width)) +
#' annotation_custom(
#' gen_grob(z, scaling = "full"),
#' xmin = 4.5, xmax = 7.5, ymin = 0.25, ymax = 2.25) +
#' geom_point() +
#' theme_minimal()
#' ragg::agg_png(
#' filename = png_f_3, width = 7,
#' height = 7, units = "in", res = 150
#' )
#' print(gg)
#' dev.off()
#' }
#'
#'
#' @family flextable print function
#' @importFrom grid gTree
gen_grob <- function(x,
...,
fit = c("auto", "width", "fixed"),
scaling = c("min", "full", "fixed"),
wrapping = TRUE,
autowidths = TRUE,
just = NULL) {
x <- flextable_global$defaults$post_process_all(x)
x <- fix_border_issues(x)
dots <- list(...)
debug <- isTRUE(dots$debug)
if (identical(fit, TRUE)) fit <- "auto"
if (identical(fit, FALSE)) fit <- "fixed"
if (identical(scaling, TRUE)) scaling <- "min"
if (identical(scaling, FALSE)) scaling <- "fixed"
fit <- match.arg(fit)
scaling <- match.arg(scaling)
wrapping <- isTRUE(wrapping)
autowidths <- isTRUE(autowidths)
if (fit %in% "fixed") {
scaling <- "fixed"
}
# generate grid data
dat <- get_grid_data(x, autowidths, wrapping)
dat <- grid_data_add_cell_info(dat, x)
dat <- grid_data_add_par_info(dat, x)
dat <- grid_data_add_chunk_info(dat, x, autowidths, wrapping)
setorderv(dat, cols = c("row_index", "col_index"))
# create grob
gr <- make_flextable_grob(dat, fit, scaling, wrapping, debug = debug)
# calculate real row heights/column widths
dims <- dim(x)
widths <- calc_grob_widths(gr, dat, dims)
heights <- calc_grob_heights(gr, dat, dims)
# set parameter list
gr$ftpar <- list(
fit = fit,
scaling = scaling,
wrapping = wrapping,
just = just,
dims = dims,
widths = widths,
heights = heights,
debug = debug
)
# set data
gr$ftdat <- dat[, c("row_index", "col_index", "rowspan", "colspan")]
# done
invisible(gr)
}
#' @importFrom grid viewport grid.layout gList vpStack
make_flextable_grob <- function(dat, fit, scaling, wrapping, debug = FALSE) {
gTree(
cl = "flextableGrob",
children = do.call(gList, mapply(
function(row_index, col_index, cell_vp, cell_width, cell_height, cell_params,
background, borders, contents_vp, chunk_data, chunk_part_data) {
# single cell grob
gTree(
children = gList(
# background
make_background_grob(background),
# borders
make_borders_grob(borders),
# debug contents rect
if (debug) {
rectGrob(
gp = gpar(col = "red", fill = "yellow", alpha = 0.15),
vp = viewport(
width = unit(1, "npc") - unit(cell_params$paddingx, "in"),
height = unit(1, "npc") - unit(cell_params$paddingy, "in")
)
)
},
# contents
make_contents_grob(
row_index = row_index,
col_index = col_index,
chunk_data = chunk_data,
chunk_part_data = chunk_part_data,
vp = contents_vp,
params = cell_params,
width = cell_width,
height = cell_height,
fit = fit,
scaling = scaling,
wrapping = wrapping,
debug = debug
),
# debug text
if (debug) {
textGrob(
label = paste(row_index, col_index, sep = "_"),
x = cell_params$just[[1]], y = cell_params$just[[2]],
gp = gpar(col = "red", fontsize = 8),
just = cell_params$justr, rot = cell_params$angle
)
}
),
vp = do.call(viewport, cell_vp),
name = paste("cell", row_index, col_index, sep = "_")
)
},
row_index = dat$row_index,
col_index = dat$col_index,
cell_vp = dat$cell_vp,
cell_width = dat$cell_width,
cell_height = dat$cell_height,
cell_params = dat$cell_params,
background = dat$background,
borders = dat$borders,
contents_vp = dat$contents_vp,
chunk_data = dat$chunk_data,
chunk_part_data = dat$chunk_part_data,
SIMPLIFY = FALSE
))
)
}
#' Calculate column widths for a flextableGrob
#' @param x The flextable grob
#' @param dat The grid_data
#' @param dims The flextable dimensions
#' @return A numeric vector with the widths
#' @noRd
calc_grob_widths <- function(x, dat, dims) {
# get the minimum width of each column
col_dat <- dat[, list(width = min(.SD$cell_width, na.rm = TRUE)), by = "col_index"]
# dims contain all columns, but some of them might have been merged
# completely into others and they should get a 0 size.
col_dat <- merge(
data.table(col_index = seq_along(dims$widths)),
col_dat,
by = "col_index", all.x = TRUE
)
col_dat[is.na(col_dat$width), "width" := 0]
col_dat$width
}
#' Calculate the row heights for a flextableGrob
#' @param x The flextable grob
#' @param dat The grid_data
#' @param dims The flextable dimensions
#' @return A numeric vector with the heights
#' @noRd
#' @importFrom grid is.grob
calc_grob_heights <- function(x, dat, dims) {
# get height info from the grob contents
content_heights <- mapply(
function(row_index, col_index, colspan) {
cell_name <- paste("cell", row_index, col_index, sep = "_")
gr <- x$children[[cell_name]]$children[["contents"]]
if (is.grob(gr)) {
list(
row_index = gr$ftpar$row_index,
col_index = gr$ftpar$col_index,
colspan = colspan,
height = gr$ftpar$height
)
}
},
dat$row_index,
dat$col_index,
dat$colspan,
SIMPLIFY = FALSE
)
height_dat <- rbindlist(content_heights)
# keep only the rows with minimum colspan
height_dat <- merge(
height_dat,
dat[, list(colspan = min(.SD$colspan)), by = c("row_index")],
by = c("row_index", "colspan")
)
# get the maximum height of each row
height_dat <- height_dat[, list(height = max(.SD$height, na.rm = TRUE)), by = "row_index"]
# dims contain all rows, but some of them might have been merged
# completely into others and they should get a 0 size.
row_dat <- merge(
data.table(row_index = seq_along(dims$heights)),
height_dat,
by = "row_index", all.x = TRUE
)
row_dat[is.na(row_dat$height), "height" := 0]
row_dat$height
}
#' @importFrom grid makeContext unit convertWidth convertHeight
#' @export
makeContext.flextableGrob <- function(x) {
params <- x$ftpar
dat <- x$ftdat
cex <- 1
lex <- 1
# dimensions of the table
widths <- params$widths
table_width <- sum(widths)
heights <- params$heights
table_height <- sum(heights)
if (params$fit %in% c("auto", "width")) {
# dimensions of drawing area
norm_width <- convertWidth(unit(1, "npc"), unitTo = "in", valueOnly = TRUE)
norm_height <- convertHeight(unit(1, "npc"), unitTo = "in", valueOnly = TRUE)
# calculate table scale factor
norm_surface <- norm_width * norm_height
table_surface <- table_width * table_height
surface_factor <- sqrt(norm_surface / table_surface)
width_factor <- norm_width / table_width
height_factor <- norm_height / table_height
# set cex/lex as the smallest of the three
scale_factor <- min(surface_factor, width_factor, height_factor)
if (params$scaling %in% "full" || (params$scaling %in% "min" && scale_factor < 1)) {
lex <- cex <- scale_factor
}
# rearrange the contents with new parameters
cell_names <- paste("cell", dat$row_index, dat$col_index, sep = "_")
for (cell_name in cell_names) {
gr <- x$children[[cell_name]]$children[["contents"]]
if (is.grob(gr)) {
x$children[[cell_name]]$children[["contents"]] <- arrange_contents_grob(gr,
width_factor = width_factor, height_factor = height_factor, cex = cex
)
}
}
# recalculate the heights
heights <- calc_grob_heights(x, dat, params$dims)
table_height <- sum(heights)
}
# create viewport
layout_just <- params$just
if (params$fit %in% "auto") {
# convert to relative widths/heights
layout_widths <- unit(widths / table_width, "npc")
layout_heights <- unit(heights / table_height, "npc")
if (is.null(layout_just)) layout_just <- c(0.5, 0.5)
} else if (params$fit %in% "width") {
layout_widths <- unit(widths / table_width, "npc")
layout_heights <- unit(heights, "in")
if (is.null(layout_just)) layout_just <- c(0.5, 1)
} else {
layout_widths <- unit(widths, "in")
layout_heights <- unit(heights, "in")
if (is.null(layout_just)) layout_just <- c(0, 1)
}
layout_vp <- viewport(
gp = gpar(
cex = cex,
lex = lex
),
layout = grid.layout(
nrow = length(heights),
ncol = length(widths),
widths = layout_widths,
heights = layout_heights,
just = layout_just
)
)
if (is.null(x$vp)) {
x$vp <- layout_vp
} else {
x$vp <- vpStack(x$vp, layout_vp)
}
# done
x
}
#' plot a flextable grob
#'
#' @param x a flextableGrob object
#' @param ... additional arguments passed to other functions
#' @importFrom grid grid.newpage grid.draw pushViewport popViewport
#' @export
plot.flextableGrob <- function(x, ...) {
grid.newpage()
# leave a 5pt margin around the plot
pushViewport(viewport(
width = unit(1, "npc") - unit(10, "pt"),
height = unit(1, "npc") - unit(10, "pt")
))
grid.draw(x)
popViewport()
invisible(x)
}
#' @title Get optimal width and height of a flextable grob
#' @description returns the optimal width and height for the grob,
#' according to the grob generation parameters.
#' @param x a flextableGrob object
#' @return a named list with two elements, `width` and `height`.
#' Values are expressed in inches.
#' @examples
#' ftab <- flextable(head(iris))
#' gr <- gen_grob(ftab)
#' dim(gr)
#' @export
dim.flextableGrob <- function(x) {
list(width = sum(x$ftpar$widths, na.rm = TRUE), height = sum(x$ftpar$heights, na.rm = TRUE))
}
# contents grob -----------------------------------------------------------
#' @importFrom data.table is.data.table .GRP
make_contents_grob <- function(row_index, col_index,
chunk_data, chunk_part_data,
vp, params, ...) {
if (!is.data.table(chunk_data) || !nrow(chunk_data) ||
!is.data.table(chunk_part_data) || !nrow(chunk_part_data)) {
return(NULL)
}
dots <- list(...)
params <- c(list(
row_index = row_index,
col_index = col_index
), params, dots)
# create contents grob
gr <- gTree(
vp = do.call(viewport, vp),
name = "contents"
)
# create chunk grobs
gr$ftgrobs <- chunk_data[,
list(grobs = list(make_chunk_grob(.GRP, .SD, params))),
by = "chunk_index"
]$grobs
# set data
gr$ftdat <- chunk_part_data
# set parameters
gr$ftpar <- params
# arrange grob to get the real height
# if the fit is fixed, make the changes permanent
gr <- arrange_contents_grob(gr, do_update_grob = params$fit %in% "fixed")
# done
gr
}
#' @importFrom grid setChildren
arrange_contents_grob <- function(x,
width_factor = 1,
height_factor = 1,
cex = 1,
do_update_grob = TRUE) {
params <- x$ftpar
dat <- x$ftdat
if (nrow(dat) > 1) {
# calculate real max width for the contents
if (params$angle == 0) {
max_width <- (params$width - params$paddingx) * width_factor
} else {
max_width <- (max(params$height, params$min_height, params$max_height, na.rm = TRUE) -
params$paddingy) * height_factor
}
child_count <- .chunk_index <- width <- NULL
if (params$wrapping) {
filtered <- c(
unlist(dat[child_count > 0 & width * cex <= max_width, "children_index"]),
unlist(dat[child_count > 0 & width * cex > max_width, ".chunk_index"])
)
dat <- dat[!.chunk_index %in% filtered, , drop = FALSE]
}
n <- nrow(dat)
if (n > 1) {
# loop the chunk parts and arrange them in rows/columns
# according to their width
row_index <- integer(n)
col_index <- integer(n)
cur_width <- 0
cur_row <- 1L
cur_col <- 1L
cur_chunk <- 0L
for (i in seq_len(n)) {
item <- dat[i, ]
cur_width <- cur_width + (item$width * cex)
if (item$is_newline) {
# if it's the first item in first row, add it, else ignore it
if (cur_row == 1 && cur_col == 1) {
row_index[[i]] <- cur_row
col_index[[i]] <- cur_col
}
cur_row <- cur_row + 1L
cur_col <- 1L
cur_width <- 0
cur_chunk <- 0L
} else if (params$wrapping && cur_width > max_width) {
if (cur_col == 1) {
# if it's the first item in row, add it and go to next row
row_index[[i]] <- cur_row
col_index[[i]] <- cur_col
cur_row <- cur_row + 1L
cur_col <- 1L
cur_width <- 0
cur_chunk <- 0L
} else {
# else go to next row and then add it
cur_row <- cur_row + 1L
cur_col <- 1L
if (item$is_whitespace) {
# ignore whitespace in line break
cur_width <- 0
cur_chunk <- 0L
} else {
row_index[[i]] <- cur_row
col_index[[i]] <- cur_col
cur_col <- cur_col + 1L
cur_width <- (item$width * cex)
cur_chunk <- item$chunk_index
}
}
} else {
if (cur_row > 1L && cur_col == 1L && item$is_whitespace) {
# ignore whitespace
cur_width <- 0
cur_chunk <- 0L
} else {
# set the row
row_index[[i]] <- cur_row
# if the item is from a different chunk than the previous
if (item$chunk_index != cur_chunk) {
# set the column
col_index[[i]] <- cur_col
# and go to next column
cur_col <- cur_col + 1L
} else {
# set it to the previous column
col_index[[i]] <- cur_col - 1
}
cur_chunk <- item$chunk_index
}
}
}
dat$row_index <- row_index
dat$col_index <- col_index
dat <- dat[row_index > 0, ]
}
}
make_row_grob <- function(row_index, grobs, layout) {
# when there are more than one grobs in a row
# we need to align them to their baseline
# because they might have different font sizes
# use flextableRowPartGrob class for that
if (length(grobs) > 1) {
grob_names <- sapply(grobs, `[[`, "name")
grobs <- lapply(grobs, function(gr) {
class(gr) <- c("flextableRowPartGrob", class(gr))
gr$ftsiblings <- grob_names
names(gr$ftsiblings) <- grob_names
gr
})
}
if (params$debug) {
grobs <- c(grobs, list(
rectGrob(
gp = gpar(lwd = 0.5, col = "red", fill = NA)
)
))
}
gTree(
children = do.call(gList, grobs),
vp = viewport(
layout.pos.row = row_index,
layout.pos.col = 1,
layout = layout
),
name = paste("chunk_row", row_index, sep = "_")
)
}
make_col_grob <- function(col_index, chunk_index,
txt_data, from_.chunk_index, to_.chunk_index,
width, height) {
gr <- x$ftgrobs[[chunk_index]]
params <- gr$ftpar
if (inherits(gr, "text") && params$txt_data != txt_data) {
gr$label <- calc_grid_label(
x = txt_data,
valign = params$valign,
is_underlined = params$is_underlined,
is_bold = params$is_bold,
is_italic = params$is_italic
)
gr$name <- paste(gr$name, "seq", from_.chunk_index, "to", to_.chunk_index, sep = "_")
}
gr$vp <- viewport(
layout.pos.row = 1,
layout.pos.col = col_index
)
if (inherits(gr, "flextableRasterChunk")) {
gr$vp <- vpStack(
gr$vp,
viewport(
width = unit(width * cex, "in"),
height = unit(height * cex, "in")
)
)
}
gr
}
if (nrow(dat) > 1) {
# group data by row, column and chunk
col_dat <- dat[, list(
from = min(.SD$.chunk_index),
to = max(.SD$.chunk_index),
txt_data = paste0(.SD$txt_data, collapse = ""),
width = sum(.SD$width),
height = max(.SD$ascent) + max(.SD$descent)
), by = c("row_index", "col_index", "chunk_index")]
if (do_update_grob) {
# create grobs for columns
col_dat[, "grob" := mapply(
make_col_grob,
.SD$col_index,
.SD$chunk_index,
.SD$txt_data,
.SD$from,
.SD$to,
.SD$width,
.SD$height,
SIMPLIFY = FALSE
)]
}
# group data by row
row_dat <- col_dat[, list(
grobs = list(.SD$grob),
layout = list(grid.layout(
nrow = 1,
ncol = nrow(.SD),
widths = unit(.SD$width * cex, "in"),
# heights = unit(max(.SD$height * cex), "in"),
just = params$justr
)),
width = sum(.SD$width * cex),
height = max(.SD$height * cex)
), by = "row_index"]
if (do_update_grob) {
# create grobs for rows
row_dat[, "grob" := mapply(
make_row_grob,
.SD$row_index,
.SD$grobs,
.SD$layout,
SIMPLIFY = FALSE
)]
children <- do.call(gList, row_dat$grob)
}
widths <- row_dat$width
heights <- row_dat$height
} else {
if (do_update_grob) {
children <- gList(make_col_grob(
1L, dat$chunk_index,
dat$txt_data, dat$.chunk_index, dat$.chunk_index,
dat$width, dat$height
))
}
widths <- dat$width * cex
heights <- dat$height * cex
}
if (do_update_grob) {
# set viewport layout
x$vp$layout <- grid.layout(
nrow = length(children),
ncol = 1,
heights = unit(heights, "in"),
widths = unit(max(widths), "in"),
just = params$justr
)
}
# calculate real height of the contents
if (params$angle == 0) {
height <- sum(heights) + params$paddingy
} else {
height <- max(widths) + params$paddingy
}
height <- min(
max(height, params$min_height, na.rm = TRUE), params$max_height,
na.rm = TRUE
)
# set the height info
x$ftpar$height <- height
if (do_update_grob) {
# set the children and return the updated grob
setChildren(x, children)
} else {
# just return the grob
x
}
}
# misc grobs --------------------------------------------------------------
#' @importFrom grid textGrob rasterGrob
make_chunk_grob <- function(chunk_index, dat, params) {
if (dat$is_text) {
gr <- textGrob(
label = calc_grid_label(
x = paste(dat$txt_data[[1]], collapse = ""),
valign = dat$valign,
is_underlined = dat$is_underlined,
is_bold = dat$is_bold,
is_italic = dat$is_italic
),
gp = dat$gp[[1]]
)
# store info for label
gr$ftpar <- list(
txt_data = dat$txt_data[[1]],
valign = dat$valign,
is_underlined = dat$is_underlined,
is_bold = dat$is_bold,
is_italic = dat$is_italic
)
} else if (dat$is_raster) {
if (inherits(dat$img_data[[1]], "flextableRasterChunk")) {
gr <- rasterGrob(
image = dat$img_data[[1]],
width = unit(1, "npc"),
height = unit(1, "npc"),
gp = dat$gp[[1]]
)
class(gr) <- c("flextableRasterChunk", class(gr))
} else {
gr <- rasterGrob(
image = dat$img_data[[1]],
gp = dat$gp[[1]]
)
}
} else {
gr <- rasterGrob(
image = blank_raster_image,
width = unit(1, "npc"),
height = unit(1, "npc"),
gp = dat$gp[[1]]
)
}
if (!dat$shading_color %in% "transparent" || params$debug) {
class(gr) <- c("flextableHighlightedGrob", class(gr))
gr$highlight_gp <- gpar(
lwd = 0.5,
fill = dat$shading_color,
col = ifelse(params$debug, "black", dat$shading_color)
)
}
gr$name <- paste("chunk", chunk_index, sep = "_")
gr
}
#' @importFrom grid rectGrob
make_background_grob <- function(args) {
if (length(args) > 0) {
args$name <- "background"
do.call(rectGrob, args)
}
}
#' @importFrom grid segmentsGrob
make_segment_grob <- function(args) {
if (length(args) > 0) {
do.call(segmentsGrob, args)
}
}
make_borders_grob <- function(args) {
if (length(args) > 0) {
segments <- lapply(args, make_segment_grob)
if (length(segments) > 0) {
gTree(
children = do.call(gList, segments),
name = "borders"
)
}
}
}
#' @importFrom grid drawDetails grid.rect grobWidth grobHeight grobDescent
#' @export
drawDetails.flextableHighlightedGrob <- function(x, recording) {
if (!is.null(x$highlight_gp)) {
# draw a rect around the grob, taking into account the descent
# as grobHeight does not include it
descent <- grobDescent(x)
if (inherits(x, "text") && is.expression(x$label)) {
yadd <- descent
yoffset <- unit(0, "npc")
} else {
yadd <- descent * 2
yoffset <- descent / 2
}
grid.rect(
x = x$x,
y = x$y - yoffset,
just = x$just,
width = grobWidth(x),
height = grobHeight(x) + yadd,
gp = x$highlight_gp
)
}
NextMethod()
}
#' @export
drawDetails.flextableRowPartGrob <- function(x, recording) {
# When different chunks are parts of a row, they must be aligned in their baseline.
# At this point they are aligned in their middle (as vjust is 0.5).
# So we must offset them according to the tallest grob.
# We use grobHeight from grid because it is most accurate and it excludes ascent and descent.
# x$ftsiblings contains the names of all the grobs in the row
if (length(x$ftsiblings) > 1) {
# calculate half height of the grobs
halfheights <- sapply(x$ftsiblings, function(z) {
convertHeight(grobHeight(z) / 2, unitTo = "in", valueOnly = TRUE)
})
# offset y, so that it is aligned with the tallest grob
x$y <- x$y - unit(max(halfheights) - halfheights[[x$name]], "in")
# offset further for subscripts
if (inherits(x, "text") && x$ftpar$valign %in% "subscript") {
x$y <- x$y - grobDescent(x)
}
}
NextMethod()
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.