`). Lengths fix columns; `fr` tracks share the remaining width. The HTML exporter lowers the attribute to a `colgroup`; the Typst exporter uses the tracks directly.
+
+Unequal dash counts in a pipe table's separator row are shorthand for proportional `colwidths`: `-|---` sets `colwidths="1fr 3fr"`. Only dashes count; alignment colons and surrounding whitespace do not affect widths. Equal dash counts leave sizing automatic. Use an explicit attribute such as `{: colwidths="1fr 1fr"}` to request equal widths. Explicit `colwidths` overrides inferred widths. Inference applies regardless of source line length or cell content. Markdown export writes widths as an explicit attribute rather than reconstructing separator lengths.
+
+A `width` attribute on the table lowers to an inline style width: a CSS length verbatim, a bare number as px, an invalid value left as a visible attribute. It merges after `colwidths`' lowering, so an explicit width beats its `width:100%`. The Typst exporter ignores the table's `width` attribute.
## Definition lists and fenced divs
@@ -348,7 +352,7 @@ A caption line glued directly below a table becomes `caption`; its trailing attr
```markdown
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
: Delivery stages {#tbl-stages}
```
diff --git a/docs/sample.html b/docs/sample.html
index 43935db..27deb15 100644
--- a/docs/sample.html
+++ b/docs/sample.html
@@ -91,7 +91,7 @@ Block quotes and rules
Tables
| Feature | Status | Notes |
-|:--------|:------:|------:|
+|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
@@ -306,7 +306,7 @@ Captions and figures
{#fig-diagram width="180"}
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
@@ -378,7 +378,7 @@ Template tokens and filling
{{/office}}
| Item | Qty |
-|------|-----|
+|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
diff --git a/examples/sample-clean.md b/examples/sample-clean.md
index 8ef3a7d..ba3b736 100644
--- a/examples/sample-clean.md
+++ b/examples/sample-clean.md
@@ -73,7 +73,7 @@ Angle links work too: .
## Tables
| Feature | Status | Notes |
-|:--------|:------:|------:|
+|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
@@ -232,7 +232,7 @@ its trailing attribute list applies to the table:
{#fig-diagram width="180"}
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
@@ -286,7 +286,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}
| Item | Qty |
-|------|-----|
+|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
diff --git a/examples/sample-render.md b/examples/sample-render.md
index 6ce030a..2a06e7f 100644
--- a/examples/sample-render.md
+++ b/examples/sample-render.md
@@ -122,14 +122,14 @@ Angle links work too: .
`````markdown
| Feature | Status | Notes |
-|:--------|:------:|------:|
+|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
`````
| Feature | Status | Notes |
-|:--------|:------:|------:|
+|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
@@ -419,7 +419,7 @@ its trailing attribute list applies to the table:
{#fig-diagram width="180"}
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
@@ -428,7 +428,7 @@ its trailing attribute list applies to the table:
{#fig-diagram width="180"}
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
@@ -506,7 +506,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}
| Item | Qty |
-|------|-----|
+|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
@@ -523,7 +523,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}
| Item | Qty |
-|------|-----|
+|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
diff --git a/examples/sample.md b/examples/sample.md
index 8e0641d..2ed39bb 100644
--- a/examples/sample.md
+++ b/examples/sample.md
@@ -84,7 +84,7 @@ Angle links work too: .
`````markdown
| Feature | Status | Notes |
-|:--------|:------:|------:|
+|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
@@ -271,7 +271,7 @@ its trailing attribute list applies to the table:
{#fig-diagram width="180"}
| Stage | Days |
-|:------|-----:|
+|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
@@ -333,7 +333,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}
| Item | Qty |
-|------|-----|
+|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
diff --git a/src/block.rs b/src/block.rs
index 91e44bb..23d0c38 100644
--- a/src/block.rs
+++ b/src/block.rs
@@ -1432,7 +1432,7 @@ impl<'a> ContainerBuilder<'a> {
let Some(header_line) = lines.last().cloned() else { return false };
let paragraph_len = lines.len();
let Some(header) = split_table_row(&header_line) else { return false };
- let Some(aligns) = parse_table_separator(line) else { return false };
+ let Some((aligns, attrs)) = parse_table_separator(line) else { return false };
if header.len() != aligns.len() { return false; }
if self.record_trace {
let lead = self.cur_offset + (line.len() - line.trim_start().len());
@@ -1443,7 +1443,7 @@ impl<'a> ContainerBuilder<'a> {
}
let head = header.into_iter().map(|cell| cell.trim().to_string()).collect();
let table = BuildKind::Table {
- attrs: Attr::default(),
+ attrs,
caption: None,
row_tokens: Vec::new(),
head: vec![draft_inline_table_row(head, &aligns)],
@@ -3003,15 +3003,17 @@ fn raw_table_cells(line: &str) -> Vec {
cells
}
-fn parse_table_separator(line: &str) -> Option> {
+fn parse_table_separator(line: &str) -> Option<(Vec, Attr)> {
let cells = split_table_row(line)?;
let mut aligns = Vec::new();
+ let mut widths = Vec::new();
for cell in cells {
let c = cell.trim();
let left = c.starts_with(':');
let right = c.ends_with(':');
let dashes = c.trim_matches(':');
if dashes.is_empty() || !dashes.chars().all(|x| x == '-') { return None; }
+ widths.push(dashes.len());
aligns.push(match (left, right) {
(true, true) => Align::Center,
(true, false) => Align::Left,
@@ -3019,7 +3021,11 @@ fn parse_table_separator(line: &str) -> Option> {
_ => Align::None,
});
}
- Some(aligns)
+ let mut attrs = Attr::default();
+ if widths.windows(2).any(|w| w[0] != w[1]) {
+ attrs.set_pair("colwidths", widths.iter().map(|w| format!("{w}fr")).collect::>().join(" "));
+ }
+ Some((aligns, attrs))
}
pub(crate) fn paragraph_interrupts(line: &str) -> bool { starts_block(line) || list_interrupts_paragraph(line) || def_marker(line).is_some() }
diff --git a/tests/canonical.rs b/tests/canonical.rs
index a9b134f..0fc68ab 100644
--- a/tests/canonical.rs
+++ b/tests/canonical.rs
@@ -12,6 +12,17 @@ fn panel_titles_and_nested_panels_roundtrip() {
assert!(!html.contains("
diff --git a/tests/source/cmark-gfm/spec.txt b/tests/source/cmark-gfm/spec.txt
index 922010e..ff38c67 100644
--- a/tests/source/cmark-gfm/spec.txt
+++ b/tests/source/cmark-gfm/spec.txt
@@ -3376,7 +3376,7 @@ they are. Likewise, use of leading and trailing pipes may be inconsistent:
```````````````````````````````` example table
| abc | defghi |
-:-: | -----------:
+:-: | -:
bar | baz
.
diff --git a/tests/source/php-markdown-extra.mdtest/Tables.text b/tests/source/php-markdown-extra.mdtest/Tables.text
index 713cd89..cc3b4e8 100644
--- a/tests/source/php-markdown-extra.mdtest/Tables.text
+++ b/tests/source/php-markdown-extra.mdtest/Tables.text
@@ -60,7 +60,7 @@ Table alignement:
Table alignement (alternate spacing):
| Default | Right | Center | Left |
-| --------- | :-------- | :-------: | --------: |
+| ------- | :------- | :-------: | -------: |
| Long Cell | Long Cell | Long Cell | Long Cell |
| Cell | Cell | Cell | Cell |
diff --git a/tests/test_focused.py b/tests/test_focused.py
index 3e3510f..c5e46e5 100644
--- a/tests/test_focused.py
+++ b/tests/test_focused.py
@@ -140,6 +140,30 @@ def test_table_ial_line_attaches():
assert html.startswith('')
assert '{: .c}' not in html
+
+def test_table_dash_widths():
+ from mdhtml import mdhtml2md
+ for sep, widths in (("|---|---------|", "3fr 9fr"), (" :--- | ---------: ", "3fr 9fr"), ("|:-:|---|", "1fr 3fr")):
+ src = f"| A | B |\n{sep}\n| x | [link](https://example.com) |\n"
+ html = md2mdhtml(src)
+ assert f'colwidths="{widths}"' in html
+ assert_html(html, md2mdhtml(src + f'{{: colwidths="{widths}"}}'))
+ assert_html(md2mdhtml(mdhtml2md(html)), html)
+ nested = "> - | A | B |\n> |:---|---------:|\n> | x | y |\n"
+ assert 'colwidths="3fr 9fr"' in md2mdhtml(nested)
+
+
+def test_table_dash_widths_auto_and_explicit():
+ for sep in ("|---|---|", "|:---|---:|", "|:---:| --- |", "|------|------|"):
+ assert "colwidths" not in md2mdhtml(f"| A | B |\n{sep}\n| x | y |\n")
+ assert "colwidths" not in md2mdhtml("| A |\n|:-----:|\n| x |\n")
+ src = "| A | B |\n|---|---------|\n| x | y |\n"
+ ial = '{: colwidths="1fr 1fr"}'
+ for authored in (ial + "\n" + src, src + ial, src + ': Caption {colwidths="1fr 1fr"}'):
+ html = md2mdhtml(authored)
+ assert 'colwidths="1fr 1fr"' in html
+ assert 'colwidths="3fr 9fr"' not in html
+
def test_raw_attribute_blocks_and_inlines():
html = md2mdhtml('```{=docx}\n\n```\n')
assert html == '\n'