vincentarelbundock / tinytable

Simple and Customizable Tables in `R`
https://vincentarelbundock.github.io/tinytable
GNU General Public License v3.0
211 stars 18 forks source link

Added escape characters in typst #233

Closed marcboschmatas closed 7 months ago

marcboschmatas commented 7 months ago

I have made a few changes and it seems to be working fine and not interfering format_tt() too much. Ref https://github.com/vincentarelbundock/tinytable/issues/232

---
title: "prova"
format: typst
---

```{r}
library(tinytable)
modelsummary::modelsummary(lm(mpg ~ cyl, data = mtcars), output = "tinytable", stars = TRUE) |> 
  format_tt(escape = TRUE) |> 
  style_tt(i = 6, bold = TRUE) |> 
  style_tt(i = 7, italic = TRUE) |> 
  style_tt(i = 8, strikeout = TRUE)
thumbdrives <- data.frame(
  date_lookup = as.Date(c("2024-01-15", "2024-01-18", "2024-01-14", "2024-01-16")),
  price = c(18.49, 19.99, 24.99, 24.99),
  price_rank = c(1, 2, 3, 3),
  memory = c(16e9, 12e9, 10e9, 8e9),
  speed_benchmark = c(0.6, 0.73, 0.82, 0.99)
)

tt(thumbdrives) |>
  format_tt(escape = TRUE) |> 
  format_tt(i = 0, escape = TRUE) |> 
  format_tt(j = 1, fn = scales::label_date("%e %b", locale = "fr")) |>
  format_tt(j = 2, fn = scales::label_currency()) |>
  format_tt(j = 3, fn = scales::label_ordinal()) |> 
  format_tt(j = 4, fn = scales::label_bytes()) |> 
  format_tt(j = 5, fn = scales::label_percent())  |>
  format_tt(escape = TRUE)
// Some definitions presupposed by pandoc's typst output.
#let blockquote(body) = [
  #set text( size: 0.92em )
  #block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body]
]

#let horizontalrule = [
  #line(start: (25%,0%), end: (75%,0%))
]

#let endnote(num, contents) = [
  #stack(dir: ltr, spacing: 3pt, super[#num], contents)
]

#show terms: it => {
  it.children
    .map(child => [
      #strong[#child.term]
      #block(inset: (left: 1.5em, top: -0.4em))[#child.description]
      ])
    .join()
}

// Some quarto-specific definitions.

#show raw.where(block: true): block.with(
    fill: luma(230), 
    width: 100%, 
    inset: 8pt, 
    radius: 2pt
  )

#let block_with_new_content(old_block, new_content) = {
  let d = (:)
  let fields = old_block.fields()
  fields.remove("body")
  if fields.at("below", default: none) != none {
    // TODO: this is a hack because below is a "synthesized element"
    // according to the experts in the typst discord...
    fields.below = fields.below.amount
  }
  return block.with(..fields)(new_content)
}

#let empty(v) = {
  if type(v) == "string" {
    // two dollar signs here because we're technically inside
    // a Pandoc template :grimace:
    v.matches(regex("^\\s*$")).at(0, default: none) != none
  } else if type(v) == "content" {
    if v.at("text", default: none) != none {
      return empty(v.text)
    }
    for child in v.at("children", default: ()) {
      if not empty(child) {
        return false
      }
    }
    return true
  }

}

#show figure: it => {
  if type(it.kind) != "string" {
    return it
  }
  let kind_match = it.kind.matches(regex("^quarto-callout-(.*)")).at(0, default: none)
  if kind_match == none {
    return it
  }
  let kind = kind_match.captures.at(0, default: "other")
  kind = upper(kind.first()) + kind.slice(1)
  // now we pull apart the callout and reassemble it with the crossref name and counter

  // when we cleanup pandoc's emitted code to avoid spaces this will have to change
  let old_callout = it.body.children.at(1).body.children.at(1)
  let old_title_block = old_callout.body.children.at(0)
  let old_title = old_title_block.body.body.children.at(2)

  // TODO use custom separator if available
  let new_title = if empty(old_title) {
    [#kind #it.counter.display()]
  } else {
    [#kind #it.counter.display(): #old_title]
  }

  let new_title_block = block_with_new_content(
    old_title_block, 
    block_with_new_content(
      old_title_block.body, 
      old_title_block.body.body.children.at(0) +
      old_title_block.body.body.children.at(1) +
      new_title))

  block_with_new_content(old_callout,
    new_title_block +
    old_callout.body.children.at(1))
}

#show ref: it => locate(loc => {
  let target = query(it.target, loc).first()
  if it.at("supplement", default: none) == none {
    it
    return
  }

  let sup = it.supplement.text.matches(regex("^45127368-afa1-446a-820f-fc64c546b2c5%(.*)")).at(0, default: none)
  if sup != none {
    let parent_id = sup.captures.first()
    let parent_figure = query(label(parent_id), loc).first()
    let parent_location = parent_figure.location()

    let counters = numbering(
      parent_figure.at("numbering"), 
      ..parent_figure.at("counter").at(parent_location))

    let subcounter = numbering(
      target.at("numbering"),
      ..target.at("counter").at(target.location()))

    // NOTE there's a nonbreaking space in the block below
    link(target.location(), [#parent_figure.at("supplement") #counters#subcounter])
  } else {
    it
  }
})

// 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead
#let callout(body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black) = {
  block(
    breakable: false, 
    fill: background_color, 
    stroke: (paint: icon_color, thickness: 0.5pt, cap: "round"), 
    width: 100%, 
    radius: 2pt,
    block(
      inset: 1pt,
      width: 100%, 
      below: 0pt, 
      block(
        fill: background_color, 
        width: 100%, 
        inset: 8pt)[#text(icon_color, weight: 900)[#icon] #title]) +
      block(
        inset: 1pt, 
        width: 100%, 
        block(fill: white, width: 100%, inset: 8pt, body)))
}

#let article(
  title: none,
  authors: none,
  date: none,
  abstract: none,
  cols: 1,
  margin: (x: 1.25in, y: 1.25in),
  paper: "us-letter",
  lang: "en",
  region: "US",
  font: (),
  fontsize: 11pt,
  sectionnumbering: none,
  toc: false,
  toc_title: none,
  toc_depth: none,
  doc,
) = {
  set page(
    paper: paper,
    margin: margin,
    numbering: "1",
  )
  set par(justify: true)
  set text(lang: lang,
           region: region,
           font: font,
           size: fontsize)
  set heading(numbering: sectionnumbering)

  if title != none {
    align(center)[#block(inset: 2em)[
      #text(weight: "bold", size: 1.5em)[#title]
    ]]
  }

  if authors != none {
    let count = authors.len()
    let ncols = calc.min(count, 3)
    grid(
      columns: (1fr,) * ncols,
      row-gutter: 1.5em,
      ..authors.map(author =>
          align(center)[
            #author.name \
            #author.affiliation \
            #author.email
          ]
      )
    )
  }

  if date != none {
    align(center)[#block(inset: 1em)[
      #date
    ]]
  }

  if abstract != none {
    block(inset: 2em)[
    #text(weight: "semibold")[Abstract] #h(1em) #abstract
    ]
  }

  if toc {
    let title = if toc_title == none {
      auto
    } else {
      toc_title
    }
    block(above: 0em, below: 2em)[
    #outline(
      title: toc_title,
      depth: toc_depth
    );
    ]
  }

  if cols == 1 {
    doc
  } else {
    columns(cols, doc)
  }
}
#show: doc => article(
  title: [prova],
  toc_title: [Table of contents],
  toc_depth: 3,
  cols: 1,
  doc,
)

#block[
```r
library(tinytable)
modelsummary::modelsummary(lm(mpg ~ cyl, data = mtcars), output = "tinytable", stars = TRUE) |> 
  format_tt(escape = TRUE) |> 
  style_tt(i = 6, bold = TRUE) |> 
  style_tt(i = 7, italic = TRUE) |> 
  style_tt(i = 8, strikeout = TRUE)

block[

import "@preview/tablex:0.0.8": tablex, hlinex, vlinex, colspanx

let nhead = 1;

let nrow = 11;

let ncol = 2;

figure(

kind: "tinytable", supplement: none, tablex( columns: ncol, header-rows: nhead, align: left + horizon, auto-lines: false,

hlinex(y: 5, start: 0, end: 2, stroke: 0.05em + black), hlinex(y: 0, start: 0, end: 2, stroke: 0.1em + black), hlinex(y: 1, start: 0, end: 2, stroke: 0.05em + black), hlinex(y: 12, start: 0, end: 2, stroke: 0.1em + black), // tinytable lines before

map-cells: cell => {

let i = (8,); let j = (0,1,);

if (i.contains(cell.y) and j.contains(cell.x)) { cell.content = strike(cell.content) }; let i = (7,); let j = (0,1,);

if (i.contains(cell.y) and j.contains(cell.x)) { cell.content = emph(cell.content) }; let i = (6,); let j = (0,1,);

if (i.contains(cell.y) and j.contains(cell.x)) { cell.content = strong(cell.content) };

if (cell.x == 1) { cell.align = center };

if (cell.x == 0) { cell.align = left };

  // tinytable cell style before
  return cell;
},

// tinytable cell content after

[ ], [(1)], [(Intercept)], [37.885***], [], [(2.074)], [cyl], [-2.876***], [], [(0.322)], [Num.Obs.], [32], [R2], [0.726], [R2 Adj.], [0.717], [AIC], [169.3], [BIC], [173.7], [Log.Lik.], [-81.653], [RMSE], [3.10],

) // end tablex ) // end figure ] ]

block[

thumbdrives <- data.frame(
  date_lookup = as.Date(c("2024-01-15", "2024-01-18", "2024-01-14", "2024-01-16")),
  price = c(18.49, 19.99, 24.99, 24.99),
  price_rank = c(1, 2, 3, 3),
  memory = c(16e9, 12e9, 10e9, 8e9),
  speed_benchmark = c(0.6, 0.73, 0.82, 0.99)
)

tt(thumbdrives) |>
  format_tt(escape = TRUE) |> 
  format_tt(i = 0, escape = TRUE) |> 
  format_tt(j = 1, fn = scales::label_date("%e %b", locale = "fr")) |>
  format_tt(j = 2, fn = scales::label_currency()) |>
  format_tt(j = 3, fn = scales::label_ordinal()) |> 
  format_tt(j = 4, fn = scales::label_bytes()) |> 
  format_tt(j = 5, fn = scales::label_percent())  |>
  format_tt(escape = TRUE)

block[

import "@preview/tablex:0.0.8": tablex, hlinex, vlinex, colspanx

let nhead = 1;

let nrow = 4;

let ncol = 5;

figure(

kind: "tinytable", supplement: none, tablex( columns: ncol, header-rows: nhead, align: left + horizon, auto-lines: false,

hlinex(y: 0, start: 0, end: 5, stroke: 0.1em + black), hlinex(y: 1, start: 0, end: 5, stroke: 0.05em + black), hlinex(y: 5, start: 0, end: 5, stroke: 0.1em + black), // tinytable lines before

map-cells: cell => {

  // tinytable cell style before
  return cell;
},

// tinytable cell content after

[date_lookup], [price], [price_rank], [memory], [speed_benchmark], [2024-01-15], [\$18.49], [1st], [16 GB], [60%], [2024-01-18], [\$19.99], [2nd], [12 GB], [73%], [2024-01-14], [\$24.99], [3rd], [10 GB], [82%], [2024-01-16], [\$24.99], [3rd], [8 GB], [99%],

) // end tablex ) // end figure ] ]

imatge

vincentarelbundock commented 7 months ago

Thanks! Out of curiosity, where did you find the list of markup symbols to escape?

marcboschmatas commented 7 months ago

In the typst docs! https://typst.app/docs/reference/syntax/

The fact that you can use both the markdown-like characters (*, _ and so on) and functions can be helpful to avoid potential conflicts I guess.

vincentarelbundock commented 7 months ago

Thanks, this is fantastic!