Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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<String> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -387,19 +387,25 @@ void computeRuns (GC gc) {
for (int i=0; i<allRuns.length - 1; i++) {
StyleItem run = allRuns[i];
if (tabsInPixels != null && run.tab) {
/*
* Whether a stop is still ahead has to be decided in points, the unit setTabs()
* defined it in. In pixels a stop the pen sits exactly on can round to one pixel
* past the pen, collapsing the tab instead of advancing to the next stop.
*/
int lineWidthInPoints = DPIUtil.pixelToPoint(lineWidth, getZoom(gc));
int tabsLength = tabsInPixels.length, j;
for (j = 0; j < tabsLength; j++) {
if (tabsInPixels[j] > 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;
}
}

Expand Down
Loading