Skip to content

Commit 78e3188

Browse files
authored
Merge branch 'master' into latex_stub_separator
2 parents 87f23a9 + 336a5f9 commit 78e3188

File tree

2 files changed

+139
-14
lines changed

2 files changed

+139
-14
lines changed

R/utils_render_latex.R

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,13 +1464,15 @@ create_body_rows_l <- function(
14641464
) {
14651465

14661466
styles_tbl <- dt_styles_get(data = data)
1467-
styles_tbl <- vctrs::vec_slice(styles_tbl, styles_tbl$locname %in% c("stub", "data", "row_groups"))
1467+
styles_tbl <- vctrs::vec_slice(
1468+
styles_tbl,
1469+
styles_tbl$locname %in% c("stub", "stub_column", "data", "row_groups")
1470+
)
14681471

14691472
# Obtain all of the visible (`"default"`), non-stub column names
14701473
# for the table from the `boxh` object
14711474
default_vars <- dt_boxhead_get_vars_default(data = data)
14721475

1473-
14741476
stub_layout <- get_stub_layout(data = data)
14751477

14761478
stub_is_2 <- length(stub_layout) > 1L
@@ -1479,17 +1481,27 @@ create_body_rows_l <- function(
14791481
stub_vars <- dt_boxhead_get_var_stub(data = data)
14801482
n_stub_cols <- if (length(stub_vars) == 1 && is.na(stub_vars)) 0 else length(stub_vars)
14811483

1484+
# For multi-column stubs, use actual column names as placeholders to enable
1485+
# per-column styling; for single-column stubs, use ::stub:: for backward compat
14821486
if (is.null(stub_layout)) {
14831487
vars <- default_vars
14841488
} else if (!is.null(stub_layout) && !stub_is_2 && stub_layout == "rowname") {
1485-
# Create a ::stub:: placeholder for each stub column
1486-
vars <- c(rep("::stub::", n_stub_cols), default_vars)
1489+
# Use actual stub column names as placeholders for proper style targeting
1490+
if (n_stub_cols > 1) {
1491+
vars <- c(paste0("::stub_", stub_vars, "::"), default_vars)
1492+
} else {
1493+
vars <- c("::stub::", default_vars)
1494+
}
14871495
} else if (!is.null(stub_layout) && !stub_is_2 && stub_layout == "group_label") {
14881496
vars <- c("::group::", default_vars)
14891497
} else if (!is.null(stub_layout) && stub_is_2) {
14901498
# When we have both group_label and rowname columns
1491-
# Create a ::stub:: placeholder for each stub column, plus the group column
1492-
vars <- c("::group::", rep("::stub::", n_stub_cols), default_vars)
1499+
# Use actual stub column names as placeholders for proper style targeting
1500+
if (n_stub_cols > 1) {
1501+
vars <- c("::group::", paste0("::stub_", stub_vars, "::"), default_vars)
1502+
} else {
1503+
vars <- c("::group::", "::stub::", default_vars)
1504+
}
14931505
}
14941506

14951507
if ("::group::" %in% vars) {
@@ -1537,11 +1549,40 @@ create_body_rows_l <- function(
15371549
} else if (
15381550
!is.na(colname_i) &&
15391551
colname_i == "::stub::" &&
1540-
"stub" %in% styles_tbl_i[["locname"]]
1552+
any(c("stub", "stub_column") %in% styles_tbl_i[["locname"]])
15411553
) {
15421554

1543-
styles_tbl_i_col <- vctrs::vec_slice(styles_tbl_i, styles_tbl_i$locname == "stub")
1544-
#styles_i_col <- styles_tbl_i_col[["styles"]]
1555+
# For single-column stubs, check both "stub" and "stub_column" locnames
1556+
styles_tbl_i_col <- vctrs::vec_slice(
1557+
styles_tbl_i,
1558+
styles_tbl_i$locname %in% c("stub", "stub_column")
1559+
)
1560+
1561+
} else if (
1562+
!is.na(colname_i) &&
1563+
grepl("^::stub_.*::$", colname_i) &&
1564+
any(c("stub", "stub_column") %in% styles_tbl_i[["locname"]])
1565+
) {
1566+
1567+
# For multi-column stubs with named placeholders (e.g., ::stub_group::)
1568+
# Extract the actual column name from the placeholder
1569+
actual_col <- gsub("^::stub_(.*)::$", "\\1", colname_i)
1570+
1571+
# Get styles for this specific stub column (stub_column locname)
1572+
# or fall back to general stub styles
1573+
stub_col_styles <- vctrs::vec_slice(
1574+
styles_tbl_i,
1575+
styles_tbl_i$locname == "stub_column" &
1576+
styles_tbl_i$colname == actual_col
1577+
)
1578+
general_stub_styles <- vctrs::vec_slice(
1579+
styles_tbl_i,
1580+
styles_tbl_i$locname == "stub"
1581+
)
1582+
styles_tbl_i_col <- vctrs::vec_rbind(
1583+
stub_col_styles,
1584+
general_stub_styles
1585+
)
15451586

15461587
} else if (
15471588
"data" %in% styles_tbl_i[["locname"]] &&
@@ -1560,17 +1601,20 @@ create_body_rows_l <- function(
15601601

15611602
styles_body <- consolidate_cell_styles_l(styles_tbl_i_col)
15621603

1563-
if(identical(colname_i,"::stub::")){
1604+
if (
1605+
identical(colname_i, "::stub::") ||
1606+
grepl("^::stub_.*::$", colname_i)
1607+
) {
15641608
colwidth_i <- dplyr::filter(
15651609
colwidth_df,
15661610
type == "stub",
15671611
)[i, ]
15681612

1569-
}else{
1613+
} else {
15701614
colwidth_i <- dplyr::filter(
15711615
colwidth_df,
15721616
var == colname_i
1573-
)
1617+
)
15741618
}
15751619

15761620
if(sum(colwidth_i$unspec < 1) > 0){
@@ -1588,13 +1632,16 @@ create_body_rows_l <- function(
15881632

15891633
} else {
15901634

1591-
if(identical(colname_i,"::stub::")){
1635+
if (
1636+
identical(colname_i, "::stub::") ||
1637+
grepl("^::stub_.*::$", colname_i)
1638+
) {
15921639
colwidth_i <- dplyr::filter(
15931640
colwidth_df,
15941641
type == "stub",
15951642
)[i, ]
15961643

1597-
}else{
1644+
} else {
15981645
colwidth_i <- dplyr::filter(
15991646
colwidth_df,
16001647
var == colname_i

tests/testthat/test-multicolumn_stub_latex.R

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,3 +702,81 @@ test_that("tab_stubhead() with styles works in LaTeX multicolumn stub", {
702702
expect_true(grepl("Model", latex_output))
703703
expect_true(grepl("Trim", latex_output))
704704
})
705+
706+
test_that("cells_stub() alignment styles work in LaTeX multicolumn stub", {
707+
708+
# Regression test for LaTeX alignment in multi-column stubs
709+
# cells_stub() should apply alignment to all stub columns in LaTeX output
710+
gt_tbl <-
711+
exibble |>
712+
gt(rowname_col = c("group", "char")) |>
713+
tab_style(
714+
style = list(cell_text(align = "right")),
715+
locations = list(cells_stub())
716+
)
717+
718+
latex_output <- as.character(as_latex(gt_tbl))
719+
720+
# Both stub columns should have right alignment applied via \multicolumn{1}{r}
721+
# Count occurrences of right-aligned multicolumn for stub cells
722+
# Each row should have 2 right-aligned stub cells (group and char columns)
723+
right_align_count <- length(
724+
gregexpr("\\\\multicolumn\\{1\\}\\{r\\}", latex_output)[[1]]
725+
)
726+
727+
# 8 rows * 2 stub columns = 16 right-aligned cells
728+
expect_gte(right_align_count, 16)
729+
730+
# Verify specific content is right-aligned
731+
expect_true(grepl("\\\\multicolumn\\{1\\}\\{r\\}\\{\\{grp\\\\_a\\}\\}", latex_output))
732+
expect_true(grepl("\\\\multicolumn\\{1\\}\\{r\\}\\{\\{apricot\\}\\}", latex_output))
733+
734+
# Test with explicit columns = NULL (should behave the same)
735+
gt_tbl_null <-
736+
exibble |>
737+
gt(rowname_col = c("group", "char")) |>
738+
tab_style(
739+
style = list(cell_text(align = "right")),
740+
locations = list(cells_stub(columns = NULL))
741+
)
742+
743+
latex_output_null <- as.character(as_latex(gt_tbl_null))
744+
745+
# Should also have right alignment for both columns
746+
right_align_count_null <- length(
747+
gregexpr("\\\\multicolumn\\{1\\}\\{r\\}", latex_output_null)[[1]]
748+
)
749+
expect_gte(right_align_count_null, 16)
750+
})
751+
752+
test_that("cells_stub() footnotes work in LaTeX multicolumn stub", {
753+
754+
# Regression test for LaTeX footnotes in multi-column stubs
755+
# cells_stub() should apply footnotes to all stub columns in LaTeX output
756+
757+
gt_tbl <-
758+
head(exibble, 4) |>
759+
gt(rowname_col = c("group", "char")) |>
760+
tab_footnote(
761+
footnote = "Test",
762+
locations = cells_stub()
763+
)
764+
765+
latex_output <- as.character(as_latex(gt_tbl))
766+
767+
# Should render without error
768+
expect_no_error(as_latex(gt_tbl))
769+
770+
# Footnote text should appear in the output
771+
expect_true(grepl("Test", latex_output))
772+
773+
# Footnote marks should be present in stub cells
774+
# Due to hierarchical stub merging: 1 mark for "grp_a" in the group column
775+
# (subsequent group rows are empty) + 4 marks for the char column
776+
# (apricot, banana, coconut, durian) = 5 marks in cells + 1 in footnote section
777+
# The format is \textsuperscript{\textit{1}}
778+
footnote_mark_count <- length(
779+
gregexpr("\\\\textsuperscript\\{\\\\textit\\{1\\}\\}", latex_output)[[1]]
780+
)
781+
expect_equal(footnote_mark_count, 6)
782+
})

0 commit comments

Comments
 (0)