Skip to content

Commit 6f2f7c9

Browse files
committed
perf(layout): sanitize table cell lines once per cell, not three times
Resolving a table ran each cell's lines through sanitizeCellLines separately in the natural-width, natural-height and resolve passes, rebuilding the list and its per-line control-character cleanup up to three times per cell. Compute the sanitized lines once when the logical grid is built (LogicalCell.sanitizedLines) and reuse them across all three passes. Output is byte-identical (sanitization is deterministic); on a large table this removes the dominant per-cell layout allocation. Covered by the existing table snapshot/visual tests. Finding 6.
1 parent 602d1bd commit 6f2f7c9

2 files changed

Lines changed: 25 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ Open cycle — bug-fix / housekeeping. Entries land here as they merge.
3939
redundant); pinned by the visual-regression suite plus a content-stream test
4040
asserting one `Tf` across many drawn spans. No public API or behaviour change.
4141

42+
- **Table cell text is sanitized once per cell instead of three times.** Resolving
43+
a table ran each cell's lines through `sanitizeCellLines` separately in the
44+
natural-width, natural-height and resolve passes, rebuilding the list and its
45+
per-line control-character cleanup up to three times per cell. The sanitized
46+
lines are now computed once when the logical grid is built and reused by all
47+
three passes. **Output is byte-identical** (sanitization is deterministic); on a
48+
large table this removes the dominant per-cell layout allocation. No public API
49+
or behaviour change.
50+
4251
### Tests / tooling
4352

4453
- **Benchmark regression gate and measurement probe (benchmarks module, not part

src/main/java/com/demcha/compose/document/layout/TableLayoutSupport.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,14 @@ record ResolvedTableLayout(
9595
* are skipped when iterating later source rows. {@code source} is the
9696
* original public {@link DocumentTableCell}, retained so the layout
9797
* can detect composed-content cells via
98-
* {@link DocumentTableCell#hasComposedContent()}.
98+
* {@link DocumentTableCell#hasComposedContent()}. {@code sanitizedLines} is
99+
* the cell's text with control characters cleaned, computed once here and
100+
* reused by the width, height and resolve passes instead of re-sanitizing the
101+
* content three times.
99102
*/
100103
private record LogicalCell(int startRow, int startColumn, int colSpan, int rowSpan,
101-
TableCellContent content, DocumentTableCell source) {
104+
TableCellContent content, DocumentTableCell source,
105+
List<String> sanitizedLines) {
102106
}
103107

104108
/**
@@ -207,7 +211,7 @@ static ResolvedTableLayoutWithContents resolveTableLayout(TableNode node,
207211
width,
208212
height,
209213
yOffset,
210-
sanitizeCellLines(logical.content()),
214+
logical.sanitizedLines(),
211215
style,
212216
fillInsets(stylesGrid, rowIndex, logical.startColumn(), logical.colSpan()),
213217
borderSides(stylesGrid, rowIndex, logical.startColumn(), logical.colSpan())));
@@ -298,7 +302,7 @@ private static double naturalCellHeight(LogicalCell logical,
298302
Padding padding = style.padding() == null ? Padding.zero() : style.padding();
299303
return prepared.measureResult().height() + padding.vertical();
300304
}
301-
return cellNaturalHeight(logical.content(), style, measurement);
305+
return cellNaturalHeight(logical.sanitizedLines(), style, measurement);
302306
}
303307

304308
static PreparedNode<TableNode> sliceTablePreparedNode(TableNode source,
@@ -510,8 +514,9 @@ private static List<List<LogicalCell>> buildLogicalRows(TableNode node, int colu
510514
occupied[r][c] = true;
511515
}
512516
}
517+
TableCellContent content = toTableCell(cell);
513518
logical.add(new LogicalCell(rowIndex, col, cell.colSpan(), cell.rowSpan(),
514-
toTableCell(cell), cell));
519+
content, cell, sanitizeCellLines(content)));
515520
col += cell.colSpan();
516521
}
517522
if (sourceIdx < source.size()) {
@@ -572,7 +577,7 @@ private static double[] resolveNaturalColumnWidths(TableNode node,
572577
}
573578
int col = logical.startColumn();
574579
singleCellRequired[col] = Math.max(singleCellRequired[col],
575-
cellNaturalWidth(logical.content(), stylesGrid[rowIndex][col], measurement));
580+
cellNaturalWidth(logical.sanitizedLines(), stylesGrid[rowIndex][col], measurement));
576581
}
577582
}
578583

@@ -596,7 +601,7 @@ private static double[] resolveNaturalColumnWidths(TableNode node,
596601
}
597602
int startCol = logical.startColumn();
598603
int endCol = startCol + logical.colSpan();
599-
double need = cellNaturalWidth(logical.content(), stylesGrid[rowIndex][startCol], measurement);
604+
double need = cellNaturalWidth(logical.sanitizedLines(), stylesGrid[rowIndex][startCol], measurement);
600605
double have = sumRange(widths, startCol, endCol);
601606
if (need <= have + EPS) {
602607
continue;
@@ -669,22 +674,22 @@ private static double[] resolveFinalColumnWidths(TableNode node,
669674
return finalWidths;
670675
}
671676

672-
private static double cellNaturalWidth(TableCellContent cell,
677+
private static double cellNaturalWidth(List<String> sanitizedLines,
673678
TableCellLayoutStyle style,
674679
TextMeasurementSystem measurement) {
675680
Padding padding = style.padding() == null ? Padding.zero() : style.padding();
676681
double maxWidth = 0.0;
677-
for (String line : sanitizeCellLines(cell)) {
682+
for (String line : sanitizedLines) {
678683
maxWidth = Math.max(maxWidth, measurement.textWidth(style.textStyle(), line));
679684
}
680685
return maxWidth + padding.horizontal();
681686
}
682687

683-
private static double cellNaturalHeight(TableCellContent cell,
688+
private static double cellNaturalHeight(List<String> sanitizedLines,
684689
TableCellLayoutStyle style,
685690
TextMeasurementSystem measurement) {
686691
Padding padding = style.padding() == null ? Padding.zero() : style.padding();
687-
int lineCount = Math.max(1, sanitizeCellLines(cell).size());
692+
int lineCount = Math.max(1, sanitizedLines.size());
688693
return (lineCount * measurement.lineHeight(style.textStyle()))
689694
+ ((lineCount - 1) * tableCellLineSpacing(style))
690695
+ padding.vertical();

0 commit comments

Comments
 (0)