Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ S3method(plot,gt_tbl)
S3method(print,gt_group)
S3method(print,gt_tbl)
S3method(print,rtf_text)
S3method(print,typst_text)
S3method(resolve_location,cells_body)
S3method(resolve_location,cells_column_labels)
S3method(resolve_location,cells_column_spanners)
Expand Down Expand Up @@ -64,6 +65,7 @@ export(as_gtable)
export(as_latex)
export(as_raw_html)
export(as_rtf)
export(as_typst)
export(as_word)
export(cell_borders)
export(cell_fill)
Expand Down Expand Up @@ -140,6 +142,7 @@ export(fmt_scientific)
export(fmt_spelled_num)
export(fmt_tf)
export(fmt_time)
export(fmt_typst)
export(fmt_units)
export(fmt_url)
export(from_column)
Expand Down Expand Up @@ -236,6 +239,7 @@ export(text_case_match)
export(text_case_when)
export(text_replace)
export(text_transform)
export(typst)
export(unit_conversion)
export(vars)
export(vec_fmt_bytes)
Expand Down
20 changes: 11 additions & 9 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# gt (development version)

* Added native Typst output support. Tables can now be rendered directly to Typst markup using `as_typst()`, saved to `.typ` files with `gtsave()`, and automatically rendered in Quarto documents with `format: typst`. This produces idiomatic Typst tables with proper `table.header()` for page-break repetition, `#super[]`/`#sub[]` for footnotes and units, cell styling via `table.cell()` properties, and support for all major `tab_options()` including borders, fonts, colors, padding, row striping, and column label customization. New exported functions: `as_typst()`, `typst()` (text helper), and `fmt_typst()` (raw Typst cell formatter). The `fmt_markdown()` function also gains a Typst handler for proper bold, italic, strikethrough, and link conversion. (@malcolmbarrett, #2134)

* Expand functionality of `gt_group()` to allow `gt_group` objects to be combined with `gt_tbls` (#2128)

# gt 1.3.0
Expand Down Expand Up @@ -109,7 +111,7 @@
* Fixed an issue in `fmt_number()` where `drop_trailing_dec_mark` would be ignored if `use_seps = FALSE` (#1961). (@olivroy, #1962).

* Fixed an issue where `fmt_markdown()` could create strange output in Quarto (html and Typst formats) (#1957). (@olivroy, #1958, [quarto-dev/quarto-cli#11932](https://github.com/quarto-dev/quarto-cli/issues/11932), [quarto-dev/quarto-cli#11610](https://github.com/quarto-dev/quarto-cli/issues/11610)).

* The default table position in LaTeX is now "t" instead of "!t" (@AaronGullickson, #1935).

* Fixed an issue where cross-references would fail in bookdown::html_document2 (@olivroy, #1948)
Expand Down Expand Up @@ -146,9 +148,9 @@

* Interactive tables now respect more styling options, namely: `column_labels.background.color`, `row_group.background.color`, `row_group.font.weight`, `table_body.hlines.style`, `table.font.weight`, `table.font.size`, and `stub.font.weight`. (#1693)

* `opt_interactive()` now works when columns are merged with `cols_merge()`. (@olivroy, #1785)
* `opt_interactive()` now works when columns are merged with `cols_merge()`. (@olivroy, #1785)

* `opt_interactive()` now works when columns are substituted with `sub_*()`. (@olivroy, #1759)
* `opt_interactive()` now works when columns are substituted with `sub_*()`. (@olivroy, #1759)

* More support for `cells_stubhead()` styling and footnotes in interactive tables.

Expand Down Expand Up @@ -340,7 +342,7 @@

* Most functions now produce better error messages if not provided with a `gt_tbl` object. (#1504, #1624)

* The URL formatting through `fmt_url()` has been improved by preventing link text breaking across lines (#1509). (#1537)
* The URL formatting through `fmt_url()` has been improved by preventing link text breaking across lines (#1509). (#1537)

* We now remove some unnecessary newlines in the HTML text produced by `as_raw_html()`, which caused an issue when integrating **gt** tables into **blastula** email messages (#1506). (#1520)

Expand All @@ -364,7 +366,7 @@

* There are two basic types of nanoplots available: `"line"` and `"bar"`. A line plot shows individual data points and has smooth connecting lines between them to allow for easier scanning of values. You can opt for straight-line connections between data points, or, no connections at all (it's up to you). The data you feed into a line plot can consist of a single vector of values (resulting in equally-spaced *y* values), or, you can supply two vectors representative of *x* and *y*.

* A bar plot is built a little bit differently. The focus is on evenly-spaced bars (requiring a single vector of values) that project from a zero line, clearly showing the difference between positive and negative values.
* A bar plot is built a little bit differently. The focus is on evenly-spaced bars (requiring a single vector of values) that project from a zero line, clearly showing the difference between positive and negative values.

* By default, any type of nanoplot will have basic interactivity. One can hover over the data points and vertical guides will display values ascribed to each. A guide on the left-hand side of the plot area will display the minimal and maximal *y* values on hover.

Expand Down Expand Up @@ -428,7 +430,7 @@

* The `gtsave()` function now works with `gt_group` objects (usually generated through `gt_split()` or `gt_group()`) (#1354). (#1365)

* All `gt_group` objects can now be printed using R Markdown or Quarto (#1286). (#1332)
* All `gt_group` objects can now be printed using R Markdown or Quarto (#1286). (#1332)

* When using `fmt_currency()` with a locale value set, **gt** will now use that to automatically select the locale's default currency. While some countries can have multiple currencies, we opt for the most-widely used currency (users could alternatively specify the currency code and `info_currencies()` contains all supported currencies used in the package) (#1346). (#1347)

Expand Down Expand Up @@ -693,15 +695,15 @@

* The `fmt_fraction()` formatter was added, allowing for flexible formatting of numerical values to mixed fractions of configurable accuracy (#402). (#753)

* Added the `opt_horizontal_padding()` and `opt_vertical_padding()` functions to easily expand or contract an HTML table in the horizontal and vertical directions (#868). (#882)
* Added the `opt_horizontal_padding()` and `opt_vertical_padding()` functions to easily expand or contract an HTML table in the horizontal and vertical directions (#868). (#882)

* There is now a `locale` argument in the `gt()` function. If set, formatter functions like `fmt_number()` will automatically use this global locale while formatting. There also remains the option to override the global locale with any non-`NULL` value set for `locale` within a `fmt_*()` call (#682). (#866)

## Minor improvements and bug fixes

* There is now more flexibility, improved documentation, and more testing/reliability for the date/time formatting functions (`fmt_date()`, `fmt_time()`, and `fmt_datetime()`). Now, `Date` and `POSIXct` columns are allowed to be formatted with these functions. With `fmt_datetime()`, we can even supply a format code for generation of custom dates/times (#612, #775, #800). (#801)

* Footnote marks for HTML tables now have an improved appearance. They are slightly larger, set better against the text they follow, and, asterisks are specially handled such that their sizing is consistent with other marks (#511). (#876)
* Footnote marks for HTML tables now have an improved appearance. They are slightly larger, set better against the text they follow, and, asterisks are specially handled such that their sizing is consistent with other marks (#511). (#876)

* Further improving support for color value inputs, **gt** now allows shorthand hexadecimal color values (like `#333`) and the use of the `transparent` CSS color keyword (#839, #856). (#870)

Expand Down Expand Up @@ -755,7 +757,7 @@ This release focuses on improvements to two main areas:
* References to columns (by way of the `columns` argument in many **gt** functions) now better adhere to **tidyselect** semantics.
* Instead of using `columns = vars(a, b)`, we now use `columns = c(a, b)` (`columns = c("a", "b")` also works, and this type of expression always has been an option in **gt**).
* Other **tidyselect** idioms should also work; things like using `where()` to target columns (e.g., `gt(exibble) %>% cols_hide(columns = where(is.numeric))` will hide all numeric columns) and negation (e.g., `columns = -c(a, b)`) function as expected.

## Breaking changes and deprecations

* Column labels subordinate to column spanner labels had their alignment forced to be `"center"` but now there is no specialized alignment of column labels under spanners. Should you need the old behavior, `tab_style()` can be used along with `cell_text(align = "center")` for all columns that live under spanners. (#662)
Expand Down
161 changes: 161 additions & 0 deletions R/export.R
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,167 @@ as_latex <- function(data) {
}
}

# as_typst() -------------------------------------------------------------------
#' Output a **gt** object as Typst
#'
#' @description
#'
#' Get the Typst content from a `gt_tbl` object as a single-element character
#' vector. This object can be used with `writeLines()` to generate a valid .typ
#' file, or it will be automatically rendered in Quarto documents targeting
#' Typst output.
#'
#' @inheritParams gtsave
#'
#' @return An object of class `typst_text`.
#'
#' @family table export functions
#' @section Function ID:
#' 13-10
#'
#' @section Function Introduced:
#' `v0.13.0` (2026-03-26)
#'
#' @export
as_typst <- function(data) {

# Perform input object validation
stop_if_not_gt_tbl(data = data)

# Build all table data objects through a common pipeline
data <- build_data(data = data, context = "typst")

# Composition of Typst -------------------------------------------------------

# Global set rules — font first, then fill, then size (matches Typst convention)
set_rules <- ""

# Font names — filter out CSS-only names that Typst can't resolve.
# If font_names is NULL or empty, don't emit #set text(font:) so the
# table inherits the document's font settings (important for Quarto/Typst
# documents that set their own fonts).
css_only_fonts <- c("-apple-system", "BlinkMacSystemFont", "system-ui",
"Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji")
font_names <- dt_options_get_value(data = data, option = "table_font_names")
if (!is.null(font_names) && length(font_names) > 0L && !identical(font_names, "")) {
typst_fonts <- font_names[!font_names %in% css_only_fonts]
if (length(typst_fonts) > 0L) {
font_str <- paste0("\"", typst_fonts, "\"", collapse = ", ")
set_rules <- paste0(set_rules, "#set text(font: (", font_str, "))\n")
}
}

# Font color — use light color if background is dark
font_color <- dt_options_get_value(data = data, option = "table_font_color")
font_color_light <- dt_options_get_value(data = data, option = "table_font_color_light")
bg_color_val <- dt_options_get_value(data = data, option = "table_background_color")

if (!is.null(bg_color_val) && nzchar(bg_color_val) && bg_color_val != "#FFFFFF") {
tryCatch({
rgb_vals <- grDevices::col2rgb(bg_color_val)
luminance <- (0.299 * rgb_vals[1] + 0.587 * rgb_vals[2] + 0.114 * rgb_vals[3]) / 255
if (luminance < 0.5 && !is.null(font_color_light) && nzchar(font_color_light)) {
font_color <- font_color_light
}
}, error = function(e) NULL)
}

if (!is.null(font_color) && nzchar(font_color)) {
typst_color <- css_color_to_typst(font_color)
if (!is.null(typst_color)) {
set_rules <- paste0(set_rules, "#set text(fill: ", typst_color, ")\n")
}
}

font_size <- dt_options_get_value(data = data, option = "table_font_size")
if (!is.null(font_size) && nzchar(font_size) && font_size != "16px") {
typst_size <- css_length_to_typst_text_size(font_size)
if (!is.null(typst_size)) {
set_rules <- paste0(set_rules, "#set text(size: ", typst_size, ")\n")
}
}

font_weight <- dt_options_get_value(data = data, option = "table_font_weight")
if (!is.null(font_weight) && nzchar(font_weight) && font_weight != "normal") {
typst_weight <- css_weight_to_typst(font_weight)
set_rules <- paste0(set_rules, "#set text(weight: \"", typst_weight, "\")\n")
}

font_style <- dt_options_get_value(data = data, option = "table_font_style")
if (!is.null(font_style) && nzchar(font_style) && font_style != "normal") {
set_rules <- paste0(set_rules, "#set text(style: \"", font_style, "\")\n")
}

# Create the heading component (title/subtitle above the table)
heading_component <- create_heading_component_t(data = data)

# Create the table start (#table( with columns, align, stroke, inset)
table_start <- create_table_start_t(data = data)

# Create the columns component (table.header with heading + spanners + labels)
# Heading goes inside table.header() for page-break repetition
columns_component <- create_columns_component_t(data = data, heading_content = heading_component)

# Create the body component (data rows, group rows, summary rows)
body_component <- create_body_component_t(data = data)

# Create the table end (bottom rule + closing paren)
table_end <- create_table_end_t(data = data)

# Create the footer component (footnotes + source notes below the table)
footer_component <- create_footer_component_t(data = data)

# Check for table_caption to wrap in #figure()
table_caption <- dt_options_get_value(data = data, option = "table_caption")

# Compose the table body (start through end)
# Heading and footer go INSIDE the table as table.cell rows
table_body <- paste0(
table_start,
columns_component,
body_component,
footer_component,
table_end
)

# Wrap in #figure() if caption is set
if (!is.null(table_caption) && !is.na(table_caption) && nzchar(table_caption)) {
caption_text <- process_text(table_caption, context = "typst")
table_id <- dt_options_get_value(data = data, option = "table_id")
label_str <- if (!is.null(table_id) && !is.na(table_id) && nzchar(table_id)) {
paste0(" <", table_id, ">")
} else {
""
}
table_body <- paste0(
"#figure(\n",
table_body,
", caption: [", caption_text, "],\n",
")", label_str, "\n"
)
}

# Table width — wrap in #block(width:) if not auto
table_width <- dt_options_get_value(data = data, option = "table_width")
if (!is.null(table_width) && table_width != "auto") {
typst_width <- css_length_to_typst(table_width)
if (!is.null(typst_width)) {
table_body <- paste0("#block(width: ", typst_width, ")[\n", table_body, "]\n")
}
}

# Compose the Typst output (heading + footer are inside table_body)
typst_str <- paste0(
set_rules,
table_body
)

class(typst_str) <- "typst_text"

typst_str
}

# as_rtf() ---------------------------------------------------------------------
#' Output a **gt** object as RTF
#'
Expand Down
11 changes: 9 additions & 2 deletions R/fmt.R
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,8 @@ context_missing_text <- function(missing_text, context) {
html = ,
grid = ,
latex = ,
word =
word = ,
typst =
{
if (!is_asis && missing_text == "---") {
"\U02014"
Expand Down Expand Up @@ -923,7 +924,8 @@ context_plusminus_mark <- function(plusminus_mark, context) {
html = ,
latex = ,
grid = ,
word =
word = ,
typst =
{
if (!is_asis && plusminus_mark == " +/- ") {
" \U000B1 "
Expand Down Expand Up @@ -986,6 +988,7 @@ context_lte_mark <- function(context) {
context,
grid = ,
word = ,
typst = ,
html = "\U02264",
latex = "$\\leq$",
"<="
Expand All @@ -1002,6 +1005,7 @@ context_gte_mark <- function(context) {
context,
grid = ,
word = ,
typst = ,
html = "\U02265",
latex = "$\\geq$",
">="
Expand All @@ -1016,6 +1020,7 @@ context_minus_mark <- function(context) {

switch(
context,
typst = ,
html = "\U02212",
"-"
)
Expand Down Expand Up @@ -1086,6 +1091,7 @@ context_exp_marks <- function(context) {
html = c("&nbsp;\U000D7&nbsp;10<sup style='font-size: 65%;'>", "</sup>"),
latex = c(" $\\times$ 10\\textsuperscript{", "}"),
rtf = c(" \\'d7 10{\\super ", "}"),
typst = c(" \U000D7 10#super[", "]"),
word = c(" \U000D7 10^", ""),
c(" \U000D7 10^", "")
)
Expand All @@ -1112,6 +1118,7 @@ context_exp_str <- function(context, exp_style) {
html = "<sub style='font-size: 65%;'>10</sub>",
latex = "{}_10",
rtf = "{\\sub 10}",
typst = "#sub[10]",
word = "10^",
"E"
)
Expand Down
Loading