diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/TextLayoutWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/TextLayoutWin32Tests.java index 33957562d0a..7a384620f92 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/TextLayoutWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/TextLayoutWin32Tests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Yatta Solutions + * Copyright (c) 2024, 2026 Yatta Solutions and others * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,16 +15,26 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.widgets.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.params.*; +import org.junit.jupiter.params.provider.*; @ExtendWith(PlatformSpecificExecutionExtension.class) @ExtendWith(WithMonitorSpecificScalingExtension.class) class TextLayoutWin32Tests { final static String text = "This is a text for testing."; + private static final String MONOSPACED_FONT = "Courier New"; + private static final int TAB_STOP_TOLERANCE_IN_POINTS = 2; @Test public void testGetBoundPublicAPIshouldReturnTheSameValueRegardlessOfZoomLevel() { @@ -70,4 +80,51 @@ public void testCalculateGetBoundsWithVerticalIndent() { assertEquals(unscaledBounds.height, scaledBounds.height, 1, "The public API for getBounds with vertical indent > 0 should give a similar result for any zoom level"); } + @ParameterizedTest + @ValueSource(ints = { 100, 125, 150, 175, 200 }) + public void testTabAfterSpacesReachesTheNextTabStop(int zoom) { + Display display = Display.getDefault(); + assumeTrue(isFontInstalled(display, MONOSPACED_FONT), MONOSPACED_FONT + " is not installed"); + + List violations = new ArrayList<>(); + for (int fontHeight = 8; fontHeight <= 20; fontHeight++) { + Font font = Font.win32_new(new Font(display, MONOSPACED_FONT, fontHeight, SWT.NORMAL), zoom); + for (int tabLength : new int[] { 2, 3, 4, 8 }) { + String spaces = " ".repeat(tabLength); + // StyledText derives its single tab stop from the width of a run of spaces + int tabWidth = boundsWidth(display, font, null, spaces); + int spacesThenTab = boundsWidth(display, font, new int[] { tabWidth }, spaces + "\t"); + int twiceTheSpaces = boundsWidth(display, font, new int[] { tabWidth }, spaces + spaces); + if (Math.abs(spacesThenTab - twiceTheSpaces) > TAB_STOP_TOLERANCE_IN_POINTS) { + violations.add(fontHeight + "pt with tab length " + tabLength + ": width " + spacesThenTab + + " instead of " + twiceTheSpaces); + } + } + } + + assertTrue(violations.isEmpty(), "A tab placed exactly on a tab stop must advance to the next one, but at zoom " + + zoom + "% it did not for " + violations); + } + + private static int boundsWidth(Display display, Font font, int[] tabs, String content) { + TextLayout layout = new TextLayout(display); + try { + layout.setFont(font); + layout.setTabs(tabs); + layout.setText(content); + return layout.getBounds().width; + } finally { + layout.dispose(); + } + } + + private static boolean isFontInstalled(Display display, String name) { + for (FontData fontData : display.getFontList(null, true)) { + if (name.equalsIgnoreCase(fontData.getName())) { + return true; + } + } + return false; + } + } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/TextLayout.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/TextLayout.java index 0dd41a0f02b..bd2723ed7df 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/TextLayout.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/TextLayout.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2022 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -387,19 +387,25 @@ void computeRuns (GC gc) { for (int i=0; i lineWidth) { + if (tabs[j] > lineWidthInPoints) { run.width = tabsInPixels[j] - lineWidth; break; } } if (j == tabsLength) { - int tabX = tabsInPixels[tabsLength-1]; - int lastTabWidth = tabsLength > 1 ? tabsInPixels[tabsLength-1] - tabsInPixels[tabsLength-2] : tabsInPixels[0]; - if (lastTabWidth > 0) { - while (tabX <= lineWidth) tabX += lastTabWidth; - run.width = tabX - lineWidth; + int tabXInPoints = tabs[tabsLength-1]; + int lastTabWidthInPoints = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0]; + if (lastTabWidthInPoints > 0) { + while (tabXInPoints <= lineWidthInPoints) tabXInPoints += lastTabWidthInPoints; + run.width = DPIUtil.pointToPixel(tabXInPoints, getZoom(gc)) - lineWidth; } }