Tables in Gemtext, the non-hacky way

2023-06-30 | #tables #gemtext | @Acidus

There is a lot to unpack in @freezr's recent post about 4 years of Gemini. However something that I felt compelled to write about was their suggestion to add table syntax to gemtext.

Four years of Geminiโ€ฆ And so what?

Specifically:

Basic table management through client. Tables are difficult to make fluid but are annoying to write manually, so we can stick with the verbatim line, but we can ask the clients to format the text for us.

They then propose a domain specific language to overload a preformatted line with extra meta data to tell the client how to render a table

 ```(5,4) B-asic
 Ro1Co1, Ro1Co2, Ro1Co3, Ro1Co4
 Ro2Co1, Ro2Co2, Ro2Co3, Ro2Co4
 Ro3Co1, Ro3Co2, Ro3Co3, Ro3Co4
 Ro4Co1, Ro4Co2, Ro4Co3, Ro4Co4
 Ro5Co1, Ro5Co2, Ro5Co3, Ro5Co4
 ```

Please don't do this. Here is why:

It's way too primitive...

First of all, it's a very naive system. It covers only the most primitive use case.

Tables are something that, at first glance, seem really easy, but need to be incredibly flexible. Here are just a few use cases that @freezr's approach won't handle.

None of this is supported by @freezr proposal. They do however have an option to denote a header row at the top of the table, or to have headers on both the top row *and* left side column. But strangely not a way to have a header on the left side column.

Their suggestion for more advanced uses cases is:

"In case to render more complex case the use of a CSV files can be an alternative"

๐Ÿ˜ฎ ๐Ÿคฎ

... and yet radically complicates clients

Let's see where this proposal breaks down in even the most trivial example. Here is a table using @freezr's syntax

 ```(4,2) B-asic
 Food, Grade
 Apple, 99
 Rice, 5
 Raspberry Cheesecake Crumble, 1000
 ```

Pretend you are a client. How wide should the first column be? The text "Food", "Apple", and "Rice" and all short. But "Raspberry Cheesecake Crumble" is super long. A naive rendering might look like this:

+------------------------------+------+
|Food                          |Grade |
+------------------------------+------+
|Apple                         |99    |
+------------------------------+------+
|Rice                          |5     |
+------------------------------+------+
|Raspberry Cheesecake Crumble  |1000  |
+------------------------------+------+

Before a client could render the first line of the table, it would need to first know that the first column needs to be 30 characters wide to fit everything. But the client won't know this until it reads the 5th line of the table definition and sees the cell value "Raspberry Cheesecake Crumble."

This requires "state." Whenever a "table line" is encountered, a client would need to read the entire preformatted blob. It would then need to figure out how wide to make each column. It would then need to figure out where to wrap columns. There is tons of complexity here.

Aside: What do web browsers do?

Readers may have noticed that the issue I describe isn't unique to tables in gemtext. Web browsers, when encountering a <TABLE> tag, have the exact same issue: How does the browser know what width to draw the table columns? Guess what? It doesn't know! Instead it draws what makes sense, and as it encounters new rows with cells that require a change to the width, it redraws the table.

If you watch a web browser rendering a very large table, you'll see it redraw the entire table as it encounters different cells of different lengths. In some cases, large tables will actually cause a browser to freeze rendering. The difference is that Web browsers can handle this because they have the flexibility to "go back" and redraw something.

Gemtext is Line Oriented

Gemini clients, on the other hand, render 1 line at a time, as those lines are read. They can't/don't "go back" to a previous line and redraw it. That concept is completely foreign to how Gemini client's are written, because it breaks the fundamental concept of Gemtext's "line oriented" design. With @freezr's proposal, clients can no longer just render a line, how that line is rendered is now based on the contents of other lines. This breaks gemtext's line oriented concept and adds a ton of complexity to the clients.

(Aside: Technically, the "preformatted" line type has state as well. It toggles fixed-width rendering or not. However this is totally optional, and can be implemented as a single boolean by a client. Regardless, I'd say its the exception that proves the line oriented Gemtext rule.)

๐Ÿ”ฅ Burn baby burn ๐Ÿ”ฅ

So how do we add tables to a gemtext document? Simple. Don't have the client interpret a domain-specific language to render a table. Instead, *YOU* render the table however you want, and "burn" that into the gemtext by just putting it in a preformatted text section, like this:

 ```
 +------------------------------+------+
 |Food                          |Grade |
 +------------------------------+------+
 |Apple                         |99    |
 +------------------------------+------+
 |Rice                          |5     |
 +------------------------------+------+
 |Raspberry Cheesecake Crumble  |1000  |
 +------------------------------+------+
 ```

This way, you are free to handle tables however you want. Cell alignment, special formatting for headers, visually pleasing text wrapping, whatever. So I could render that same table above, and burn it into gemtext like this instead:

 ```
 +------------------------------+------+
 |             FOOD             | GRADE|
 +------------------------------+------+
 |Apple                         |    99|
 +------------------------------+------+
 |Rice                          |     5|
 +------------------------------+------+
 |Raspberry Cheesecake Crumble  |  1000|
 +------------------------------+------+
 ```

Or like this:

 ```
 +-----------+------+
 |FOOD       | GRADE|
 +-----------+------+
 |Apple      |    99|
 +-----------+------+
 |Rice       |     5|
 +-----------+------+
 |Raspberry  |      |
 |Cheesecake |  1000|
 |Crumble    |      |
 +-----------+------+
 ```

Or even like this:

 ```
 FOOD       โ”‚ GRADE
 โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•
 Apple      โ”‚    99
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€
 Rice       โ”‚     5 
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€
 Raspberry  โ”‚       
 Cheesecake โ”‚  1000 
 Crumble    โ”‚       
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€
 ```

Whenever I've written gemtext that has tables, like the WAREZ ebook, or many pages of the Apple II History project (both found on the root of this capsule), I've burned the tables into the gemtext.

But that's a lot of work!

Is it? I created those tables above by hand. If it's a real issue, you can just use a program. Do you really think "how do I generate a table in text" is a problem no one has had in the last 50 years of command line computing? Of course not, there are plenty of "CSV to ASCII table"-style command line programs out there that can do it for you. Why would we expect the client to do it for us?

But clients should control how things are rendered!

Sure, this is mostly true. How header lines, link lines, and list lines look is controlled by the client. And I think this is actually a strength of Gemtext.

However, we don't expect Gemini clients to control how our ASCII art is rendered. We don't define ASCII art using LOGO or some domain specific language and let the client render it. Instead authors create ASCII art however they want, and burn it into the gemtext by including it between preformatted lines. Why would we treat tables any differently?

"But... what if we could?"

"What if we could?" from "The Core" (Animated GIF)

To be clear: WE SHOULD NOT DO THIS!

But...

... if I *had* to include a table in gemtext...

I wouldn't create a new domain specific language to define a table, where I would just have to speed run all the lessons and evolution of the <TABLE> tag and all its attributes and child tags to support complex tables. I would just put TABLE HTML into the gemtext like this:

 ```
 <table>
 <tr><th>Food</th><th>Grade</th></tr>
 <tr><td>Apple</td><td>99</td></tr>
 <tr><td>Rice</td><td>C5/td></tr>
 <tr><td>Raspberry Cheesecake Crumble</td><td>1000</td></tr>
 </table>
 ```

And then let clients render it if they support it. Other clients can just ignore it and render it as plain text.

In fact... if you held a gun to my head and asked me to design a generic system to embed resources into gemtext, which wouldn't violate Gemini's philosophy of "1 request per resource, only make requests triggered by explicit user action", I would do something like this:

 ```mime="application/x-tex"
 x^n + y^n = z^n 
 ```

 ```mime="text/html"
 <table>
 <tr><th>Food</th><th>Grade</th></tr>
 <tr><td>Apple</td><td>99</td></tr>
 <tr><td>Rice</td><td>C5/td></tr>
 <tr><td>Raspberry Cheesecake Crumble</td><td>1000</td></tr>
 </table>
 ```

 ```mime="text/x-c"
 int main()
 {
     printf("Hello World");
     return 0;
 }
 ```

Clients could choose to render that LaTeX math formula, or that HTML table, or colorize that C source code. Or not.

Luckily, we don't need to think about that, because I'm definitely not proposing that ๐Ÿ™ˆ

Summary

We don't need to complicate clients by asking them to render tables:

Convert the table to text and burn it in between preformatted lines and you are good to go.

(And see what I did there to make an ordered list? BOOM! Another thing @freezr brought up that you really don't need to worry about. Client could detect that pattern if they really want to and render it in a special way, just like how clients like Lagrange detect link lines whose link text starts with an emoji and render it in a special way.)