Bug 78268 - [Graphics] GC.drawString draws at wrong vertical offset
Summary: [Graphics] GC.drawString draws at wrong vertical offset
Status: CLOSED WONTFIX
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 3.1   Edit
Hardware: PC Linux-Motif
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact: Felipe Heidrich CLA
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 77735
  Show dependency tree
 
Reported: 2004-11-10 04:57 EST by Tom Hofmann CLA
Modified: 2017-06-19 20:43 EDT (History)
3 users (show)

See Also:


Attachments
screenshot (7.85 KB, image/png)
2004-11-10 05:07 EST, Tom Hofmann CLA
no flags Details
screenshot showing the discrepancy between styled text string drawing and the ruler (7.15 KB, image/png)
2004-11-10 05:14 EST, Tom Hofmann CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Tom Hofmann CLA 2004-11-10 04:57:44 EST
I20041109

I have a case where I draw onto a GC using similar to this:

gc.fillRectangle(x, y, width, lineheight);
gc.drawString(s, x, y, true);

However the string is printed such that its upper bound is right at 'y' instead
of leaving some room as usual. I will attach a screenshot. 

I cannot reproduce the problem when using the snippet below - I am not sure
whether the problem stems from using multiple nested Canvas'? 

Please comment.


Snippet that does show the expected behavior:

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;

public class MotifStringDrawing {

public static void main (String [] args) {
	
	final Display display = new Display ();
	final Shell shell = new Shell (display, SWT.SHELL_TRIM);
	Rectangle rectangle= shell.computeTrim(50, 50, 260, 460);
	shell.setBounds(rectangle);
	shell.open ();
	final Composite composite = new Composite (shell, SWT.BORDER);
	composite.setBounds(30, 30, 200, 400);
	
	int xorigin= 20;
	int yorigin= 20;
	final int width= 20;
	Canvas canvas= new Canvas(composite, SWT.NONE);
	canvas.setBounds(xorigin, yorigin, width, 200);
	canvas.addPaintListener(new PaintListener() {
		public void paintControl(org.eclipse.swt.events.PaintEvent e) {
			GC gc = e.gc;
			FontMetrics metrics= gc.getFontMetrics();
			int lineheight= metrics.getHeight();
			gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
			int indent= 4;
			for (int i= 0; i < 10; i++) {
				gc.setBackground(display.getSystemColor(i + SWT.COLOR_RED));
				gc.fillRectangle(0, i*lineheight , width, lineheight);
				gc.drawString(Integer.toString(i), indent, i*lineheight, true);
			}
		}
	});
	
	while (!shell.isDisposed ()) {
		if (!display.readAndDispatch ()) display.sleep ();
	}
	display.dispose ();
}
}
Comment 1 Tom Hofmann CLA 2004-11-10 05:07:58 EST
Created attachment 15770 [details]
screenshot

the line number ruler is a canvas. both the colored background rectangle and
the linenumber are drawn using the same y offset.
Comment 2 Tom Hofmann CLA 2004-11-10 05:14:56 EST
Created attachment 15773 [details]
screenshot showing the discrepancy between styled text string drawing and the ruler
Comment 3 Felipe Heidrich CLA 2004-11-10 17:36:35 EST
Make sure the snippet and the ruler on the workbench are using the same font. 
This looks like a font with bad metrics case.
Comment 4 Felipe Heidrich CLA 2004-11-15 11:14:09 EST
In the real code are you guys using:
FontMetrics metrics= gc.getFontMetrics();
int lineheight= metrics.getHeight();
or:
int lineheight= styledText.getLineHeight()
?

Note that later is the right code, gc#getFontMetrics returns only the metrics of
one font and styledText.getLineHeight will compute the metrics of all the fonts
in use by the styledtext (normal, bold, italic, and bold-italic) and return the
max ascent + max desent.
Comment 5 Tom Hofmann CLA 2004-11-15 11:37:32 EST
we are using the latter to compute the line heigth - when drawing the line
numbers, we compute an additional offset to draw the text using the following code: 

/**
 * Returns the difference between the baseline of the widget and the
 * baseline as specified by the font for <code>gc</code>. When drawing
 * line numbers, the returned bias should be added to obtain text line up on
 * the correct base line of the text widget.
 * 
 * @param gc the <code>GC</code> to get the font metrics from
 * @return the baseline bias to use when drawing text that is line up with
 *         <code>fCachedTextWidget</code>
 */
private int getBaselineBias(GC gc) {
	/* 
	 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951
	 * widget line height may be more than the font height used for the
	 * linenumbers, since font styles (bold, italics...) can have larger
	 * font metrics than the simple font used for the numbers.
	 */ 
	int widgetBaseline= fCachedTextWidget.getBaseline();
	FontMetrics fm = gc.getFontMetrics();
	int fontBaseline = fm.getAscent() + fm.getLeading();
	Assert.isTrue(widgetBaseline >= fontBaseline);
	int baselineBias= widgetBaseline - fontBaseline;
	return baselineBias;
}

In the motif case, there never is any baseline bias which may be due to the fact
that the StyledText implementation does not take into account 'leading' at all?
Comment 6 Felipe Heidrich CLA 2004-11-15 11:57:19 EST
Okay, the code in eclipse is right.

"StyledText implementation does not take into account 'leading' at all?"
This is not correct, StyledText considers the leading.

The problem is in the fontBaseline. You compute it using gc.getFontMetrics(),
which returns the max ascent + max leading of all font structs in the font list,
in TextLayout I use XmBaseline(fontList, string) that returns only the ascent +
leading of the font struct what will be used to draw 'string'.

Not sure, I can fix this problem in GC#drawString for you or add a new API in
SWT (something like a getMetrics that takes a string), the problem is that this
new API would only make sense in Motif where a font is a list of list of font
structs and you don't know which font will be used till you know the string.
Comment 7 Tom Hofmann CLA 2004-11-15 12:14:41 EST
> "StyledText implementation does not take into account 'leading' at all?"
> This is not correct, StyledText considers the leading.

I am wrong, sorry. I had just quickly looked at StyledTextRenderer and saw that
it only stores the ascent, but did not look how it computes it...

> The problem is in the fontBaseline. You compute it using gc.getFontMetrics(),
> which returns the max ascent + max leading of all font structs in the font 
> list

Would there be an easy way for me to measure this? Or create a new font with
just the FontData I am going to use. Say, something like this:

Font font= gc.getFont();
boolean useSimpleFont= false;
FontData[] datas= font.getFontData();
for (int i= 0; i < datas.length; i++) {
	if (SWT.SIMPLE == datas[i].getStyle()) {
		font= new Font(display, datas[i]);
		useSimpleFont= true;
		break;
	}
}
if (useSimpleFont)
	gc.setFont(font);
FontMetrics fm = gc.getFontMetrics();
if (useSimpleFont)
	font.dispose();

// measure offset as above
Comment 8 Felipe Heidrich CLA 2004-11-15 17:29:57 EST
It won't work Tom, this needs to be fixed in SWT.
I think the right thing is to fix drawString/drawText, but problem is that the
fix will make them slower (possible much slower), maybe I'll have to cache the
max baseline for a font in GCData or something.
Comment 9 Felipe Heidrich CLA 2004-11-15 17:35:32 EST
The fix makes drawString 6x slower (tested in UTF-8)
With a cache it works much better (1.1x slower).
Comment 10 Tom Hofmann CLA 2006-01-13 11:46:52 EST
Any updates on this? It seems the same problem appears on linux-gtk as well...
Comment 11 Dani Megert CLA 2006-08-04 06:01:25 EDT
ping.
Comment 12 Felipe Heidrich CLA 2006-08-08 15:13:14 EDT
GC metrics on Motif are a bit off. This simple code doesn't work:
canvas.addPaintListener(new PaintListener() {
	public void paintControl(PaintEvent e) {
		GC gc = e.gc;
		FontMetrics metrics= gc.getFontMetrics();
		gc.drawString("PQpq", xOffset, yOffset, true);
		int baseline = metrics.getAscent() + metrics.getLeading();
		gc.drawLine (xOffset, yOffset+baseline, 300, yOffset+baseline);
	}
});
The line is not at the baseline.
Inside drawText/drawString we need to fix y, something like:
y = y + fontBaseline - textBaseline;
where textBaseline is: OS.XmStringBaseline(data.font.handle, data.xmString);
and fontBaseline can be computed with:
int getFontBaseline () {
	int fontList = data.font.handle;
	/* Create a font context to iterate over each element in the font list */
	int [] buffer = new int [1];
	if (!OS.XmFontListInitFontContext (buffer, fontList)) {
		SWT.error(SWT.ERROR_NO_HANDLES);
	}
	int context = buffer [0];
	
	/* Values discovering during iteration */
	int ascent = 0;
	XFontStruct fontStruct = new XFontStruct ();
	int fontListEntry;
	int [] fontStructPtr = new int [1];
	int [] fontNamePtr = new int [1];
	
	/* Go through each entry in the font list. */
	while ((fontListEntry = OS.XmFontListNextEntry (context)) != 0) {
		int fontPtr = OS.XmFontListEntryGetFont (fontListEntry, buffer);
		if (buffer [0] == 0) {
			/* FontList contains a single font */
			OS.memmove (fontStruct, fontPtr, XFontStruct.sizeof);
			ascent = Math.max(ascent, fontStruct.ascent);
		} else {
			/* FontList contains a fontSet */
			int nFonts = OS.XFontsOfFontSet (fontPtr, fontStructPtr, fontNamePtr);
			int [] fontStructs = new int [nFonts];
			OS.memmove (fontStructs, fontStructPtr [0], nFonts * 4);
			
			/* Go through each fontStruct in the font set */
			for (int i=0; i<nFonts; i++) {
				OS.memmove (fontStruct, fontStructs[i], XFontStruct.sizeof);
				ascent = Math.max(ascent, fontStruct.ascent);
			}
		}
	}
	OS.XmFontListFreeFontContext (context);
	return ascent;
}

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Two problems:
1) performance!
2) TextLayout already had the code to "fix" y, it would have to be removed.

Comment 13 Felipe Heidrich CLA 2006-08-08 15:17:14 EDT
The other option is not to fix gc on motif. but change the application code to draw using TextLayout, I loaded org.eclipse.jface.text and changed
LineNumberRulerColumn#doPaint as follow:
	void doPaint(GC gc, ILineRange visibleLines) {
		Display display= fCachedTextWidget.getDisplay();
		
		// draw diff info
		int y= -JFaceTextUtil.getHiddenTopLinePixels(fCachedTextWidget);
		gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
		int lastLine= end(visibleLines);
		for (int line= visibleLines.getStartLine(); line < lastLine; line++) {
			int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line);
			if (widgetLine == -1)
				continue;

			int offset= fCachedTextWidget.getOffsetAtLine(widgetLine);
			int lineHeight= fCachedTextWidget.getLineHeight(offset);
			int ascent= fCachedTextWidget.getBaseline(offset);
			String s= createDisplayString(line);
			int indentation= fIndentation[s.length()];
			TextLayout layout= new TextLayout(display);
			layout.setFont(fCachedTextWidget.getFont());
			layout.setText(s); 
			layout.setAscent(ascent);
			layout.setDescent(lineHeight - ascent);
			layout.draw(gc, indentation, y);
			layout.dispose();
			y += lineHeight;
		}
	}


*-*--*-*-*-*-*-*-*-*-*-*
Doing this I think you don't even need that getBaselineBias anymore.
Comment 14 Dani Megert CLA 2006-08-09 02:55:32 EDT
>The other option is not to fix gc on motif. but change the application code to
>draw using TextLayout,
What about all other SWT clients using this?

Tom, please check whether we could use Felipe's code.
Comment 15 Tom Hofmann CLA 2006-08-09 12:34:49 EDT
Hm, just tested the TextLayout version, and while it appears to work, I also see a lot more flashing going on. This is still true if I create the TextLayout in createControl and reuse it for painting, and is not influenced by doubleBuffering (which we'll probably get rid off anyway).

Will investigate some more.
Comment 16 Felipe Heidrich CLA 2006-08-09 14:38:15 EDT
> What about all other SWT clients using this?

The problem is there and it should be fixed. I'll talk about this to SSQ and when comes back.

> I also see a lot more flashing going on.
Hmm, I don't understand why. TextLayout draws transparent (it doesn't erase background). I don't know what can be causing this flashing.
Comment 17 Dani Megert CLA 2006-11-23 05:15:35 EST
I found a similar problem with GC.drawString which fails on both WindowsXP and Linux-GTK. See bug 165640.
Comment 18 Felipe Heidrich CLA 2009-08-18 11:10:01 EDT
Your bug has been moved to triage, visit http://www.eclipse.org/swt/triage.php for more info.
Comment 19 Leo Ufimtsev CLA 2017-06-19 20:43:21 EDT
Linux Motif no longer supported.