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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ latex_examples.pdf

# Do not track lockfile in package/lib setting
uv.lock
scratch/
2 changes: 2 additions & 0 deletions great_tables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
pct,
md,
html,
typst,
google_font,
random_id,
system_fonts,
Expand All @@ -34,6 +35,7 @@
"pct",
"md",
"html",
"typst",
"google_font",
"system_fonts",
"define_units",
Expand Down
74 changes: 74 additions & 0 deletions great_tables/_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ._scss import compile_scss
from ._utils import _try_import
from ._utils_render_latex import _render_as_latex
from ._utils_render_typst import _render_as_typst

if TYPE_CHECKING:
# Note that as_raw_html uses methods on the GT class, not just data
Expand Down Expand Up @@ -349,6 +350,66 @@ def as_latex(self: GT, use_longtable: bool = False, tbl_pos: str | None = None)
return latex_table


def as_typst(self: GT) -> str:
"""
Output a GT object as Typst.

The `as_typst()` method outputs a GT object as a Typst fragment. This method is useful for when
you need to include a table as part of a Typst document or in Quarto documents that render to
Typst/PDF output.

:::{.callout-warning}
`as_typst()` is still experimental.
:::

Returns
-------
str
A Typst fragment that contains the table.

Examples
--------
Let's use a subset of the `gtcars` dataset to create a new table.

```{python}
from great_tables import GT
from great_tables.data import gtcars
import polars as pl

gtcars_mini = (
pl.from_pandas(gtcars)
.select(["mfr", "model", "msrp"])
.head(5)
)

gt_tbl = (
GT(gtcars_mini)
.tab_header(
title="Data Listing from the gtcars Dataset",
subtitle="Only five rows from the dataset are shown here."
)
.fmt_currency(columns="msrp")
)

gt_tbl
```

Now we can return the table as a string of Typst code using the `as_typst()` method.

```{python}
gt_tbl.as_typst()
```

The Typst string contains the code just for the table (it's not a complete Typst document).
This output can be useful for embedding a GT table in an existing Typst document.
"""
built_table = self._build_data(context="typst")

typst_table = _render_as_typst(data=built_table)

return typst_table


# Create a list of all selenium webdrivers
WebDrivers: TypeAlias = Literal[
"chrome",
Expand Down Expand Up @@ -436,6 +497,19 @@ def save(
```

"""
# Handle text-based output formats that don't need a browser
file_path = Path(file)

if file_path.suffix == ".typ":
typst_content = as_typst(self)
file_path.write_text(typst_content, encoding=encoding)
return self

if file_path.suffix == ".tex":
latex_content = as_latex(self)
file_path.write_text(latex_content, encoding=encoding)
return self

# Import the required packages
_try_import(name="selenium", pip_install_line="pip install selenium")

Expand Down
92 changes: 89 additions & 3 deletions great_tables/_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
is_series,
to_list,
)
from ._text import _md_html, escape_pattern_str_latex
from ._text import _md_html, escape_pattern_str_latex, escape_pattern_str_typst
from ._utils import _str_detect, _str_replace, is_valid_http_schema
from ._utils_nanoplots import _generate_nanoplot

Expand Down Expand Up @@ -394,6 +394,8 @@ def fmt_number_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -591,6 +593,8 @@ def fmt_integer_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -860,6 +864,8 @@ def fmt_scientific_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -1207,6 +1213,8 @@ def fmt_engineering_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -1460,6 +1468,8 @@ def fmt_percent_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -1746,6 +1756,8 @@ def fmt_currency_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -1869,6 +1881,8 @@ def fmt_roman_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -2126,6 +2140,8 @@ def fmt_bytes_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -2281,6 +2297,8 @@ def fmt_date_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -2425,6 +2443,8 @@ def fmt_time_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -2618,6 +2638,8 @@ def fmt_datetime_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_formatted = pattern.replace("{x}", x_formatted)

Expand Down Expand Up @@ -2826,6 +2848,8 @@ def fmt_tf_context(
# Escape LaTeX special characters from literals in the pattern
if context == "latex":
pattern = escape_pattern_str_latex(pattern_str=pattern)
elif context == "typst":
pattern = escape_pattern_str_typst(pattern_str=pattern)

x_out = pattern.replace("{x}", x_styled)
else:
Expand Down Expand Up @@ -3025,11 +3049,70 @@ def fmt_markdown_context(

x_str: str = str(x)

x_formatted = _md_html(x_str)
if context == "typst":
from ._text import _md_typst

x_formatted = _md_typst(x_str)
else:
x_formatted = _md_html(x_str)

return x_formatted


def fmt_typst(
self: GTSelf,
columns: SelectExpr = None,
rows: int | list[int] | None = None,
) -> GTSelf:
"""
Format cells as raw Typst markup.

The `fmt_typst()` method treats cell values as raw Typst markup, passing them through
without escaping in Typst output. This allows you to use Typst commands like
`#text(fill: red)[...]`, math mode `$ x^2 $`, or any other Typst syntax directly in cells.

Note that this formatter only works with Typst output (`as_typst()` or Quarto with
`format: typst`). It will raise `NotImplementedError` when rendering to HTML or LaTeX.

Parameters
----------
columns
The columns to target for formatting.
rows
The rows to target for formatting.

Returns
-------
GT
The GT object is returned.
"""

pf_format = partial(
fmt_typst_context,
data=self,
)

return fmt_by_context(self, pf_format=pf_format, columns=columns, rows=rows)


def fmt_typst_context(
x: Any,
data: GTData,
context: str,
) -> str:
if context == "html":
raise NotImplementedError("fmt_typst() is not supported in HTML output.")

if context == "latex":
raise NotImplementedError("fmt_typst() is not supported in LaTeX output.")

if is_na(data._tbl_data, x):
return x

# In typst context, pass through raw — no escaping
return str(x)


def fmt_units(
self: GTSelf,
columns: SelectExpr = None,
Expand Down Expand Up @@ -3592,6 +3675,8 @@ def _context_exp_marks(context: str) -> list[str]:
marks = [" \u00d7 10<sup style='font-size: 65%;'>", "</sup>"]
elif context == "latex":
marks = [" $\\times$ 10\\textsuperscript{", "}"]
elif context == "typst":
marks = [" \u00d7 10#super[", "]"]
else:
marks = [" \u00d7 10^", ""]

Expand Down Expand Up @@ -3634,7 +3719,7 @@ def _context_percent_mark(context: str) -> str:


def _context_dollar_mark(context: str) -> str:
if context == "latex":
if context in ("latex", "typst"):
mark = "\\$"
else:
mark = "$"
Expand Down Expand Up @@ -5542,6 +5627,7 @@ def fmt_by_context(
fns=FormatFns(
html=partial(pf_format, context="html"), # type: ignore
latex=partial(pf_format, context="latex"), # type: ignore
typst=partial(pf_format, context="typst"), # type: ignore
default=partial(pf_format, context="html"), # type: ignore
),
columns=columns,
Expand Down
3 changes: 2 additions & 1 deletion great_tables/_gt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,11 +963,12 @@ class FormatterSkipElement:
class FormatFns:
html: FormatFn | None
latex: FormatFn | None
typst: FormatFn | None
rtf: FormatFn | None
default: FormatFn | None

def __init__(self, **kwargs: FormatFn):
for format in ("html", "latex", "rtf", "default"):
for format in ("html", "latex", "typst", "rtf", "default"):
if fmt := kwargs.get(format):
setattr(self, format, fmt)

Expand Down
26 changes: 25 additions & 1 deletion great_tables/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from typing_extensions import Self, TypeAlias

from ._text import BaseText, Html, Md, _md_html
from ._text import BaseText, Html, Md, Typst, _md_html

FontStackName: TypeAlias = Literal[
"system-ui",
Expand Down Expand Up @@ -243,6 +243,30 @@ def html(text: str) -> Html:
return Html(text=text)


def typst(text: str) -> Typst:
"""Interpret input text as Typst-formatted text.

For certain pieces of text (like in column labels, table headings, or cell values) you may want
to use raw Typst markup. The `typst()` function will pass the input through without escaping
when rendering to Typst output, allowing you to use Typst commands like `#text(fill: red)[...]`
or math mode `$ x^2 $`.

Note that `typst()` content will not render correctly in HTML or LaTeX output — it is
intended for use with Typst-targeted rendering (e.g., `as_typst()` or Quarto with
`format: typst`).

Parameters
----------
text
The text that is understood to contain Typst formatting.

Examples
------
See [`GT.tab_header()`](`great_tables.GT.tab_header`).
"""
return Typst(text=text)


def random_id(n: int = 10) -> str:
"""Helper for creating a random `id` for an output table

Expand Down
Loading
Loading