Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- Spreadsheet rendering bounds columns as well as rows by the cell budget and
avoids rescanning blank spans for every empty cell.

- Browser text editors delete complete supplementary Unicode characters when
the browser supplies no deletion range, preserving valid text and undo.

Expand Down
2 changes: 1 addition & 1 deletion src/odr/html.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ struct HtmlConfig {
/// Largest sheet region written, per axis; cells past it are dropped.
std::optional<TableDimensions> spreadsheet_limit{
TableDimensions(100000, 500)};
/// Most cells written for one sheet; bounds the rows by the sheet's width.
/// Cell budget, at least one; caps columns first, then rows by that width.
std::optional<std::uint64_t> spreadsheet_cell_limit{500000};
/// Trim a sheet to the cells it uses before the limits above apply.
bool spreadsheet_limit_by_content{true};
Expand Down
13 changes: 8 additions & 5 deletions src/odr/internal/html/document_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,11 @@ TableDimensions html::sheet_rendered_extent(const Sheet &sheet,
}
end_column = std::max(1u, end_column);
if (config.spreadsheet_cell_limit) {
const std::uint64_t rows = *config.spreadsheet_cell_limit / end_column;
const std::uint64_t limit =
std::max<std::uint64_t>(1, *config.spreadsheet_cell_limit);
end_column =
static_cast<std::uint32_t>(std::min<std::uint64_t>(end_column, limit));
const std::uint64_t rows = limit / end_column;
end_row = static_cast<std::uint32_t>(
std::min<std::uint64_t>(end_row, std::max<std::uint64_t>(1, rows)));
}
Expand Down Expand Up @@ -769,10 +773,9 @@ void html::translate_sheet(const Sheet &sheet, const WritingState &state) {
// the bottom and would paint over the row below.
cell_css += "overflow:hidden;";
}
// Over the empty cells beside it, cut where the next one has something
// to show, unbounded where nothing follows. Each blank cell is walked by
// the one cell that may spill over it, so the row costs one pass.
if (!wraps && cuts_its_text && column_pixels[column_index].has_value()) {
// Only nonblank cells spill, so each blank span is scanned once.
if (!wraps && cuts_its_text && column_pixels[column_index].has_value() &&
!is_blank(cell)) {
std::optional<double> spill(0);
bool bounded = false;
for (std::uint32_t ahead = next_column; ahead < end_column; ++ahead) {
Expand Down
9 changes: 8 additions & 1 deletion test/src/html_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ TEST(html, an_editable_csv_states_that_it_takes_no_style) {
}

// The rows a sheet keeps follow how wide it turns out to be.
TEST(html, the_cell_budget_bounds_the_rows_by_the_width) {
TEST(html, the_cell_budget_bounds_columns_and_rows) {
HtmlConfig config;
config.spreadsheet_limit = TableDimensions(1000, 1000);
config.spreadsheet_cell_limit = 60;
Expand All @@ -1153,6 +1153,12 @@ TEST(html, the_cell_budget_bounds_the_rows_by_the_width) {
// the rectangle still caps a sheet the budget would let through
config.spreadsheet_limit = TableDimensions(5, 1000);
EXPECT_EQ(rendered(30, 3).rows, 5);
config.spreadsheet_limit.reset();
EXPECT_EQ(rendered(2, 80).columns, 60);
EXPECT_EQ(rendered(2, 80).rows, 1);
config.spreadsheet_cell_limit = 0;
EXPECT_EQ(rendered(2, 80).columns, 1);
EXPECT_EQ(rendered(2, 80).rows, 1);
}

// #822: an inline `style` is the one shape a browser cannot share across the
Expand Down Expand Up @@ -1622,6 +1628,7 @@ TEST(html, a_cell_spills_over_a_blank_neighbour_and_is_cut_by_a_full_one) {
EXPECT_NE(spills.find("max-width:0;white-space:nowrap;"
"clip-path:inset(0 -96px 0 0)"),
std::string::npos);
EXPECT_EQ(spills.find("nowrap;overflow:hidden"), std::string::npos);
}

// #822: and where nothing follows it at all, out onto the canvas — there is no
Expand Down
Loading