Skip to content

Commit 6312cf5

Browse files
authored
Merge pull request #1995 from thebioengineer/latex_newline
Random Latex Updates -
2 parents a062cea + 4b2403e commit 6312cf5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1062
-129
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export(info_locales)
169169
export(info_paletteer)
170170
export(info_time_style)
171171
export(info_unit_conversions)
172+
export(latex)
172173
export(local_image)
173174
export(matches)
174175
export(md)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
* as_word() now handles "<br>" line breaks for md() and html(), and no longer automatically adds "Table N" ahead of the caption.
88

9+
* Improvements to options for LaTeX including repeating Headers, removing top and bottom lines, applying font sizes, and line breaks (@thebioengineer, #1630, #1061, #1767)
10+
11+
* Add `latex()` function to let users write the LaTeX they wish to see in the table cell or text (@thebioengineer, #1912)
12+
913
# gt 1.0.0
1014

1115
## Minor improvements and bug fixes
@@ -28,6 +32,7 @@
2832

2933
* Fixed many typos in the documentation. (#1910, thanks @MichaelChirico!)
3034

35+
3136
# gt 0.11.1
3237

3338
## Breaking changes

R/dt_options.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,5 +272,8 @@ dt_options_tbl <-
272272
"quarto_disable_processing", FALSE, "quarto", "logical", FALSE,
273273
"quarto_use_bootstrap", FALSE, "quarto", "logical", FALSE,
274274
"latex_use_longtable", FALSE, "latex", "logical", FALSE,
275+
"latex_header_repeat", FALSE, "latex", "logical", FALSE,
276+
"latex_toprule", FALSE, "latex", "logical", TRUE,
277+
"latex_bottomrule", FALSE, "latex", "logical", TRUE,
275278
"latex_tbl_pos", FALSE, "latex", "value", "t",
276279
)[-1, ]

R/helpers.R

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,58 @@ html <- function(text, ...) {
137137
htmltools::HTML(text, ...)
138138
}
139139

140+
# latex() -----------------------------------------------------------------------
141+
#' Interpret input text as LaTeX-formatted text
142+
#'
143+
#' @description
144+
#'
145+
#' For certain pieces of text (like in column labels or table headings) we may
146+
#' want to express them as raw LaTeX. In fact, with LaTeX, so much more can be done for formatting.
147+
#' The `latex()` function will guard the input LaTeX from being escaped.
148+
#'
149+
#' @param text *LaTeX text*
150+
#'
151+
#' `scalar<character>` // **required**
152+
#'
153+
#' The text that is understood to be LaTeX text, which is to be preserved in
154+
#' the LaTeX output context.
155+
#'
156+
#'
157+
#' @return A character object of class `latex`. It's tagged as an latex fragment
158+
#' that is not to be sanitized.
159+
#'
160+
#' @section Examples:
161+
#'
162+
#' Use the [`exibble`] dataset to create a **gt** table. When adding a title
163+
#' through [tab_header()], we'll use the `latex()` helper to signify to **gt**
164+
#' that we're using LaTeX formatting.
165+
#'
166+
#' ```r
167+
#' exibble |>
168+
#' dplyr::select(currency, char) |>
169+
#' gt() |>
170+
#' tab_header(title = latex("\\emph{LaTeX}"))
171+
#' ```
172+
#'
173+
#' \if{html}{\out{
174+
#' `r man_get_image_tag(file = "man_latex_1.png")`
175+
#' }}
176+
#'
177+
#' @family helper functions
178+
#' @section Function ID:
179+
#' 8-2
180+
#'
181+
#' @section Function Introduced:
182+
#' `v1.0.1` (May 10, 2025)
183+
#'
184+
#' @export
185+
latex <- function(text) {
186+
187+
# Apply the `from_latex` class
188+
class(text) <- "from_latex"
189+
text
190+
}
191+
140192
# px() -------------------------------------------------------------------------
141193
#' Helper for providing a numeric value as pixels value
142194
#'

R/tab_options.R

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,26 @@
534534
#' use the `longtable` environment which will not float and may span multiple
535535
#' pages.
536536
#'
537+
#'
538+
#' @param latex.header_repeat
539+
#'
540+
#' * Specify if the header should repeat or not across pages*
541+
#'
542+
#' For Long tables, it may be desirable to have the column headers repeat on
543+
#' every page. Setting this parameter to `TRUE` will add a `\endhead` command
544+
#' after the table headers so LaTeX knows where the headers end and will
545+
#' repeat them on every page.
546+
#'
547+
#' @param latex.toprule,latex.bottomrule
548+
#'
549+
#' * Specify if an hrule should be put in the table at the top (latex.toprule) or bottom (latex.bottomrule)*
550+
#'
551+
#' By default the tables produced using latex code will include top and bottom
552+
#' lines in the table via `\toprule` and `\bottomrule`. Setting these
553+
#' parameters to `FALSE` will instead not have these commands added, which
554+
#' lets the tables be produced without the top and bottom lines.
555+
#'
556+
#'
537557
#' @param latex.tbl.pos
538558
#'
539559
#' *Specify latex floating position*
@@ -864,6 +884,9 @@ tab_options <- function(
864884
quarto.use_bootstrap = NULL,
865885
quarto.disable_processing = NULL,
866886
latex.use_longtable = NULL,
887+
latex.header_repeat = NULL,
888+
latex.toprule = NULL,
889+
latex.bottomrule = NULL,
867890
latex.tbl.pos = NULL
868891
) {
869892

R/utils.R

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,12 @@ process_text <- function(text, context = "html") {
939939

940940
return(text)
941941

942+
} else if (inherits(text, "from_latex")) {
943+
944+
text <- as.character(text)
945+
946+
return(text)
947+
942948
} else {
943949

944950
text <- escape_latex(text = text)
@@ -1126,6 +1132,8 @@ markdown_to_latex <- function(text, md_engine) {
11261132
return(NA_character_)
11271133
}
11281134

1135+
x <- gsub("<br>","..gt_linebreak_indicator..", x)
1136+
11291137
if (isTRUE(getOption("gt.html_tag_check", TRUE))) {
11301138

11311139
if (grepl("<[a-zA-Z\\/][^>]*>", x)) {
@@ -1136,11 +1144,19 @@ markdown_to_latex <- function(text, md_engine) {
11361144
}
11371145
}
11381146

1147+
11391148
if (names(md_engine_fn) == "commonmark") {
1140-
gsub("\\n$", "", md_engine_fn[[1]](text = x))
1149+
x <- gsub("\\n$", "", md_engine_fn[[1]](text = x))
11411150
} else {
1142-
gsub("\\n$", "", md_engine_fn[[1]](text = x, format = "latex"))
1151+
x <- gsub("\\n$", "", md_engine_fn[[1]](text = x, format = "latex"))
1152+
}
1153+
1154+
if(grepl("..gt\\_linebreak\\_indicator..", x, fixed = TRUE)){
1155+
x <- paste0("\\shortstack[l]{" ,gsub("..gt\\_linebreak\\_indicator..", " \\\\", x, fixed = TRUE), "}")
11431156
}
1157+
1158+
x
1159+
11441160
}
11451161
)
11461162
)

R/utils_render_latex.R

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,14 @@ latex_body_row <- function(content, type) {
8888
}
8989

9090
#' @noRd
91-
latex_heading_row <- function(content) {
91+
latex_heading_row <- function(content, header_repeat = FALSE) {
92+
9293

9394
paste0(
9495
paste(paste(content, collapse = " & "), "\\\\ \n"),
95-
"\\midrule\\addlinespace[2.5pt]\n",
96+
"\\midrule",
97+
if (header_repeat) {"\\endhead"},
98+
"\\addlinespace[2.5pt]\n",
9699
collapse = ""
97100
)
98101
}
@@ -384,14 +387,38 @@ create_heading_component_l <- function(data) {
384387
footnote_subtitle_marks <- ""
385388
}
386389

387-
title_row <- latex_group("\\large ", heading$title, footnote_title_marks)
390+
title_font_size <- dt_options_get_value(data, "heading_title_font_size")
391+
table_font_size <- dt_options_get_value(data, "table_font_size")
392+
393+
if(identical(title_font_size,"default")){
394+
title_font_size <- table_font_size
395+
}
396+
397+
title_row <- latex_group(
398+
convert_font_size_l(title_font_size),
399+
" ",
400+
heading$title,
401+
footnote_title_marks,
402+
convert_font_size_l(table_font_size)
403+
)
388404

389405
if (subtitle_defined) {
390406

407+
subtitle_font_size <- dt_options_get_value(data, "heading_subtitle_font_size")
408+
409+
if(identical(subtitle_font_size,"default")){
410+
subtitle_font_size <- table_font_size
411+
}
412+
391413
subtitle_row <-
392414
paste0(
393415
" \\\\ \n",
394-
latex_group("\\small ", heading$subtitle, footnote_subtitle_marks)
416+
latex_group(
417+
convert_font_size_l(subtitle_font_size),
418+
" ",
419+
heading$subtitle,
420+
footnote_subtitle_marks,
421+
convert_font_size_l(table_font_size))
395422
)
396423

397424
} else {
@@ -490,8 +517,10 @@ create_columns_component_l <- function(data, colwidth_df) {
490517
headings_labels <- prepend_vec(headings_labels, stub_label)
491518
}
492519

520+
header_repeat <- dt_options_get_value(data, "latex_header_repeat")
521+
493522
table_col_headings <-
494-
paste0(latex_heading_row(content = headings_labels), collapse = "")
523+
paste0(latex_heading_row(content = headings_labels, header_repeat = header_repeat), collapse = "")
495524

496525
if (spanner_row_count > 0) {
497526

@@ -595,8 +624,10 @@ create_columns_component_l <- function(data, colwidth_df) {
595624
table_col_spanners <- ""
596625
}
597626

627+
include_toprule <- dt_options_get_value(data, "latex_toprule")
628+
598629
paste0(
599-
"\\toprule\n",
630+
if (include_toprule) {"\\toprule\n"},
600631
paste0(table_col_spanners, collapse = ""),
601632
table_col_headings
602633
)
@@ -637,7 +668,7 @@ create_body_component_l <- function(data, colwidth_df) {
637668
n_rows_in_group <- n_rows_in_group + dim(list_of_summaries$summary_df_data_list[[i]])[1L]
638669
}
639670
row_splits_body[[groups_rows_df$row_start[i]]][1] <-
640-
sprintf("\\multirow{%d}{%s}{%s}",
671+
sprintf("\\multirow[t]{%d}{%s}{%s}",
641672
n_rows_in_group,
642673
if (colwidth_df$unspec[1L] == 1L) "*" else "=",
643674
groups_rows_df$group_label[i])
@@ -1027,8 +1058,11 @@ summary_rows_for_group_l <- function(
10271058
#' @noRd
10281059
create_table_end_l <- function(data) {
10291060

1061+
include_bottomrule <- dt_options_get_value(data, "latex_bottomrule")
1062+
1063+
10301064
paste0(
1031-
"\\bottomrule\n",
1065+
if(include_bottomrule){"\\bottomrule\n"},
10321066
ifelse(dt_options_get_value(data = data, option = "latex_use_longtable"),
10331067
"\\end{longtable}\n",
10341068
"\\end{tabular*}\n"),
@@ -1253,11 +1287,16 @@ convert_font_size_l <- function(x) {
12531287
large = "\\large ",
12541288
`x-large` = "\\Large ",
12551289
`xx-large` = "\\LARGE ",
1256-
`xxx-large` = "\\huge "
1290+
`xxx-large` = "\\huge ",
1291+
`default` = ""
12571292
)
12581293

1259-
if (as.character(x) %in% names(size_map))
1294+
1295+
if (as.character(x) %in% names(size_map)){
12601296
return(size_map[[x]])
1297+
}else if(grepl("(pt|%|px|in|cm|emu|em)",x)){
1298+
return(.apply_style_fontsize_l(list('cell_text' = list('size' = round(parse_font_size_l(x))))))
1299+
}
12611300

12621301
NULL
12631302
}
@@ -1492,6 +1531,7 @@ apply_cell_styles_l <- function(content, style_obj) {
14921531
# Apply changes that can be made to the bracketed environment
14931532
out_text <- paste0(
14941533
"{",
1534+
14951535
.apply_style_style_l(style_obj),
14961536
.apply_style_weight_l(style_obj),
14971537
# Can generate "\small for example
@@ -1581,9 +1621,9 @@ apply_cell_styles_l <- function(content, style_obj) {
15811621
return(
15821622
paste0(
15831623
"\\fontsize{",
1584-
style_obj[["cell_text"]][["size"]] * 0.75,
1624+
round(style_obj[['cell_text']][['size']] * 0.75),
15851625
"}{",
1586-
style_obj[["cell_text"]][["size"]] * 0.75 * 1.25,
1626+
round(style_obj[['cell_text']][['size']] * 0.75 * 1.25),
15871627
"}\\selectfont "
15881628
)
15891629
)
@@ -1669,28 +1709,28 @@ create_fontsize_statement_l <- function(data) {
16691709

16701710
fs_fmt <- "\\fontsize{%3.1fpt}{%3.1fpt}\\selectfont\n"
16711711
if (grepl(pattern = "^[[:digit:]]+(\\%|in|cm|emu|em|pt|px)$", size)) {
1712+
font_size <- parse_font_size_l(size)
1713+
## parse_font_size_l returns font size in p
1714+
fs_statement <- sprintf(fs_fmt, round(font_size*.75), round(font_size* 1.2 * .75))
1715+
} else {
1716+
return("")
1717+
}
16721718

1673-
if (endsWith(size, "%")) {
1674-
1675-
multiple <- as.numeric(gsub("%", "", size, fixed = TRUE)) / 100
1676-
fs_statement <- sprintf(fs_fmt, multiple * 12, multiple * 12 * 1.2)
1677-
1678-
} else if (endsWith(size, "pt")) {
1679-
1680-
pt_size <- as.numeric(sub("pt$", "", size))
1681-
fs_statement <- sprintf(fs_fmt, pt_size, pt_size * 1.2)
1719+
fs_statement
16821720

1683-
} else {
1721+
}
16841722

1685-
pt_size <- convert_to_px(size) * 0.75
1686-
fs_statement <- sprintf(fs_fmt, pt_size, pt_size * 1.2)
1723+
parse_font_size_l <- function(x){
1724+
if (endsWith(x, "%")) {
16871725

1688-
}
1726+
size <- (as.numeric(gsub("%", "", x, fixed = TRUE)) / 100) * 16 * (4/3) ## 12pt is default font size, 4/3 to convert to px
16891727

1690-
} else return("")
1728+
} else {
16911729

1692-
fs_statement
1730+
size <- convert_to_px(x)
1731+
}
16931732

1733+
size
16941734
}
16951735

16961736
create_colwidth_df_l <- function(data) {

images/man_latex_1.png

22.3 KB
Loading

man/adjust_luminance.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/cell_borders.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)