Hi,
I came across this bug working on a personal project that uses html2text.
Disclaimer: what follows has been written by an agent.
Summary
When a table cell contains several stacked block elements, the cell's natural width is computed as the sum of the blocks' widths instead of their maximum, as if the blocks were laid out side by side. A cell holding three 20-character paragraphs is laid out 60 columns wide.
The bug is invisible whenever the requested width is small enough to clamp the (wrong) natural width, which is probably why it has gone unnoticed. It becomes visible as soon as the rendering width is large — and it is very visible in the wild on HTML emails and newsletters, which nest a lot of stacked blocks inside layout tables.
Reproducer
fn main() {
let c = "x".repeat(20);
for (label, html) in [
("one paragraph", format!("<table><tr><td><p>{c}</p></td></tr></table>")),
("three paragraphs", format!("<table><tr><td><p>{c}</p><p>{c}</p><p>{c}</p></td></tr></table>")),
("three rows", format!("<table><tr><td>{c}</td></tr><tr><td>{c}</td></tr><tr><td>{c}</td></tr></table>")),
] {
let out = html2text::from_read(html.as_bytes(), 1000).unwrap();
let w = out.lines().map(|l| l.chars().count()).max().unwrap_or(0);
println!("--- {label}: width {w}");
println!("{out}");
}
}
Output:
--- one paragraph: width 20
────────────────────
xxxxxxxxxxxxxxxxxxxx
────────────────────
--- three paragraphs: width 60
────────────────────────────────────────────────────────────
xxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxx
────────────────────────────────────────────────────────────
--- three rows: width 20
────────────────────
xxxxxxxxxxxxxxxxxxxx
────────────────────
xxxxxxxxxxxxxxxxxxxx
────────────────────
xxxxxxxxxxxxxxxxxxxx
────────────────────
(The trailing padding of the "three paragraphs" block has been trimmed here for readability; each of those lines is really 60 columns wide.)
Expected: all three cases render 20 columns wide, since no single block is wider than 20 characters.
Measurements
Rendering the same shapes with 100-character blocks at a very large width (from_read(html, 100_000)), to read the natural width directly:
| Cell content |
Computed width |
Expected |
| 1 paragraph of 100 chars |
100 |
100 |
| 3 paragraphs of 100 chars |
300 |
100 |
3 <div>s of 100 chars |
300 |
100 |
| 3 nested tables of 100 chars |
300 |
100 |
3 lines of 100 chars separated by <br> |
302 |
100 |
| 3 table rows of 100 chars |
100 |
100 |
The last row is the interesting one: widths across rows of a column are correctly maximised. The defect is confined to blocks stacked within a single cell.
Impact
I maintain a terminal mail client that uses html2text to derive the plain-text version of HTML-only messages. On a real newsletter (a layout table nesting many stacked blocks), rendering with an effectively unbounded width produced 146 lines of 5110 columns, including one column 4322 columns wide to display 15 characters of text — while the longest sentence in the whole message is 255 columns. Rendering the same message with tables in raw mode gives 121 lines of 255 columns, which matches the actual content.
Any finite width hides the problem by clamping it, so the practical consequence is narrower than it looks: it makes large or unbounded rendering widths unusable for documents that use layout tables. I have worked around it on my side by capping the width, but the underlying computation still looks wrong.
Versions
Reproduced with 0.16.7 (crates.io) and with main at commit 2fa2c17 (version 0.17.1), with identical numbers on both.
Hi,
I came across this bug working on a personal project that uses
html2text.Disclaimer: what follows has been written by an agent.
Summary
When a table cell contains several stacked block elements, the cell's natural width is computed as the sum of the blocks' widths instead of their maximum, as if the blocks were laid out side by side. A cell holding three 20-character paragraphs is laid out 60 columns wide.
The bug is invisible whenever the requested width is small enough to clamp the (wrong) natural width, which is probably why it has gone unnoticed. It becomes visible as soon as the rendering width is large — and it is very visible in the wild on HTML emails and newsletters, which nest a lot of stacked blocks inside layout tables.
Reproducer
Output:
(The trailing padding of the "three paragraphs" block has been trimmed here for readability; each of those lines is really 60 columns wide.)
Expected: all three cases render 20 columns wide, since no single block is wider than 20 characters.
Measurements
Rendering the same shapes with 100-character blocks at a very large width (
from_read(html, 100_000)), to read the natural width directly:<div>s of 100 chars<br>The last row is the interesting one: widths across rows of a column are correctly maximised. The defect is confined to blocks stacked within a single cell.
Impact
I maintain a terminal mail client that uses html2text to derive the plain-text version of HTML-only messages. On a real newsletter (a layout table nesting many stacked blocks), rendering with an effectively unbounded width produced 146 lines of 5110 columns, including one column 4322 columns wide to display 15 characters of text — while the longest sentence in the whole message is 255 columns. Rendering the same message with tables in raw mode gives 121 lines of 255 columns, which matches the actual content.
Any finite width hides the problem by clamping it, so the practical consequence is narrower than it looks: it makes large or unbounded rendering widths unusable for documents that use layout tables. I have worked around it on my side by capping the width, but the underlying computation still looks wrong.
Versions
Reproduced with 0.16.7 (crates.io) and with
mainat commit2fa2c17(version 0.17.1), with identical numbers on both.