From 6613f1f74670c8e3be1e5e7cea3677cc2c15fc6c Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Sun, 31 May 2026 21:56:01 +0200 Subject: [PATCH] snap repaint clear/fill to integer pixels to kill seam bars cellWidth is fractional, so in repaintColumns the clearRect cleared a run's edge pixel to full transparency while the background fillRect covered that same pixel only partially (antialiased), leaving a ~1px part-transparent column that showed the near-black pane background as a thin bar 1-2 cells before the cursor. Full-row repaint hid it because the highlighted line is then one contiguous fill with no internal junction. Route the clear and the background fills through a shared columnX() that rounds each column boundary to a whole device pixel, so run edges land on integer pixels with full single coverage and adjacent runs tile seamlessly. Co-Authored-By: Claude Opus 4.8 --- .../gregor/jprototerm/TerminalPaneNode.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java b/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java index c6a2688..5c67bbb 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java @@ -825,6 +825,14 @@ final class TerminalPaneNode extends Region { return Math.ceil(TerminalMetrics.PADDING + (row + 1) * metrics.lineHeight()); } + // Left edge of a column, snapped to a whole device pixel. Both the clearRect and the + // background fillRect go through this so a run's edges land on integer pixels: at a + // fractional boundary clearRect clears the edge pixel fully while fillRect would only + // cover it partially, leaving a thin transparent (black) seam through the line. + private double columnX(int column) { + return Math.round(TerminalMetrics.PADDING + column * metrics.cellWidth()); + } + private void repaintColumns(GraphicsContext gc, RenderRow row, int startColumn, int endColumn) { if (endColumn < startColumn) { return; @@ -836,8 +844,8 @@ final class TerminalPaneNode extends Region { double contentTop = TerminalMetrics.PADDING + row.row() * lineHeight; double localCellTop = contentTop - rowTop; double baseline = TerminalMetrics.PADDING + metrics.baselineOffset() + row.row() * lineHeight - rowTop; - double x = TerminalMetrics.PADDING + startColumn * cellWidth; - double width = (endColumn - startColumn + 1) * cellWidth; + double x = columnX(startColumn); + double width = columnX(endColumn + 1) - x; gc.clearRect(x, 0.0, width, canvas.getHeight()); if (startColumn == 0) { @@ -845,7 +853,7 @@ final class TerminalPaneNode extends Region { gc.fillRect(0.0, 0.0, TerminalMetrics.PADDING, canvas.getHeight()); } if (endColumn >= row.cells().size() - 1) { - double contentRight = TerminalMetrics.PADDING + row.cells().size() * cellWidth; + double contentRight = columnX(row.cells().size()); gc.setFill(rowEdgeBackground(row, false)); gc.fillRect(contentRight, 0.0, canvas.getWidth() - contentRight, canvas.getHeight()); } @@ -860,7 +868,7 @@ final class TerminalPaneNode extends Region { return; } double contentLeft = TerminalMetrics.PADDING; - double contentRight = contentLeft + columns * metrics.cellWidth(); + double contentRight = columnX(columns); gc.setFill(rowEdgeBackground(row, true)); gc.fillRect(0.0, 0.0, contentLeft, bandHeight); gc.setFill(rowEdgeBackground(row, false)); @@ -914,11 +922,12 @@ final class TerminalPaneNode extends Region { if (background == null || endColumn < startColumn) { return; } + double left = columnX(startColumn); gc.setFill(background); gc.fillRect( - TerminalMetrics.PADDING + startColumn * cellWidth, + left, localCellTop, - (endColumn - startColumn + 1) * cellWidth, + columnX(endColumn + 1) - left, lineHeight); }