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

Caption won't appear for Typst format tables unless in text citation #204

Closed elipousson closed 7 months ago

elipousson commented 8 months ago

When using a Typst output format, {tinytable} appears to ignore the tbl-cap code block option unless the table is referenced elsewhere in the document.

vincentarelbundock commented 8 months ago

Could supply a fully reproducible (but still minimal) example. Perhaps a simple .qmd with a simple table?

There's no logic at all in the tinytable codebase to detect whether a table is referenced elsewhere in a document. Thus, my strong intuition is that this is a Quarto or knitr issue, rather than tinytable.

It would still be useful to have a clean example so we can escalate and report upstream if necessary.

Another good diagnostic step would be to try a file with tinytable-generated table, and another one with exactly the same table, but with raw Typst code rather than tinytable generation. If the same problem subsist, then we confirm that this is not tinytable's "fault".

elipousson commented 8 months ago

Here is an reproducible Quarto document (also here):

---
format: typst
---

```{r}
library(tinytable)
x <- mtcars[1:4, 1:5]
tt(x, caption = "Table caption")
#| tbl-cap: "Table caption"
tt(x)

See @tbl-tiny

#| label: tbl-tiny
#| tbl-cap: "Table caption"
tt(x)


After submitting the issue, I realized it was likely an issue with Quarto – happy to report over there if you can help me sort out the origin of the issue.
vincentarelbundock commented 8 months ago

Raw typst output for reference:

````typst // 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( toc_title: [Table of contents], toc_depth: 3, cols: 1, doc, ) #block[ ```r library(tinytable) x <- mtcars[1:4, 1:5] ``` ] #block[ ```r tt(x, caption = "Table caption") ``` #block[ #import "@preview/tablex:0.0.8": tablex, hlinex, vlinex, colspanx #let nhead = 1; #let nrow = 4; #let ncol = 5; #figure( caption: [Table caption], 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 [mpg], [cyl], [disp], [hp], [drat], [21.0], [6], [160], [110], [3.90], [21.0], [6], [160], [110], [3.90], [22.8], [4], [108], [ 93], [3.85], [21.4], [6], [258], [110], [3.08], ) // end tablex ) // end figure ] ] #block[ ```r tt(x) ``` #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 [mpg], [cyl], [disp], [hp], [drat], [21.0], [6], [160], [110], [3.90], [21.0], [6], [160], [110], [3.90], [22.8], [4], [108], [ 93], [3.85], [21.4], [6], [258], [110], [3.08], ) // end tablex ) // end figure ] ] See @tbl-tiny #block[ ```r tt(x) ``` #figure([ #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 [mpg], [cyl], [disp], [hp], [drat], [21.0], [6], [160], [110], [3.90], [21.0], [6], [160], [110], [3.90], [22.8], [4], [108], [ 93], [3.85], [21.4], [6], [258], [110], [3.08], ) // end tablex ) // end figure ] ], caption: figure.caption( position: top, [ Table caption ]), kind: "quarto-float-tbl", supplement: "Table", numbering: "1", ) ] ````
vincentarelbundock commented 8 months ago

@elipousson so there are two distinct problems.

First, the caption argument in tt() does not interact well with Quarto. I'm not sure if there's a solution for that and will need to investigate.

The second issue seems more clearly to fall in Quarto's remit. The problem is that:

  1. If label chunk option is defined, Quarto wraps the Typst code in a #figure block.
  2. If label chunk option is not defined, Quarto does not wrap the code in a #figure block.

The second behavior is probably incorrect in cases where the user supplies a tbl-cap option explicitly. I believe this should be reported as an Issue on the Quarto repository.

You can see this by inspecting the raw .typ file left over when rendering this notebook:

---
format:
  typst:
    keep-typ: true
---

```{r}
library(tinytable)
x <- mtcars[1:2, 1:2]
#| tbl-cap: "Fist table without label"
tt(x)
#| label: tbl-tiny
#| tbl-cap: "Second table with label"
tt(x)
vincentarelbundock commented 7 months ago

This is a Quarto bug, so I reported it upstream: https://github.com/quarto-dev/quarto-cli/issues/9369

Thanks again for the report.