wxStaticText stops displaying text after tab character

85 views
Skip to first unread message

Nathan Ridge

unread,
Dec 9, 2009, 11:18:04 PM12/9/09
to wx-u...@googlegroups.com

Hello,

I'm having an issue with wxStaticText on Windows where
any text after a tab character is not displayed.

I found an old bug about this: http://trac.wxwidgets.org/ticket/1897,
but it relates to wxOSX. Is it possible that it wasn't an Mac-specific
issue and that it hasn't been fixed yet?

If so, are there any workarounds I could use?

Thanks,
Nate.
_________________________________________________________________
Windows Live: Make it easier for your friends to see what you’re up to on Facebook.
http://go.microsoft.com/?linkid=9691816

Vadim Zeitlin

unread,
Dec 10, 2009, 10:14:44 AM12/10/09
to wx-u...@googlegroups.com
On Thu, 10 Dec 2009 04:18:04 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> I'm having an issue with wxStaticText on Windows where
NR> any text after a tab character is not displayed.

How can this be reproduced? This patch:

--- a/samples/minimal/minimal.cpp
+++ b/samples/minimal/minimal.cpp
@@ -172,6 +172,9 @@ MyFrame::MyFrame(const wxString& title)
CreateStatusBar(2);
SetStatusText("Welcome to wxWidgets!");
#endif // wxUSE_STATUSBAR
+
+ new wxStaticText(this, -1, "Just a label", wxPoint(10, 10));
+ new wxStaticText(this, -1, "Another\tone", wxPoint(10, 40));
}

works just fine for me: both labels are displayed correctly. Please make a
patch (please see http://trac.wxwidgets.org/wiki/HowToSubmitPatches)
allowing to see the problem.

Regards,
VZ

--
TT-Solutions: wxWidgets consultancy and technical support
http://www.tt-solutions.com/

Nathan Ridge

unread,
Dec 11, 2009, 9:40:54 PM12/11/09
to wx-u...@googlegroups.com

> From: va...@wxwidgets.org
> NR> I'm having an issue with wxStaticText on Windows where
> NR> any text after a tab character is not displayed.
>
> How can this be reproduced?

The following patch to minimal.cpp of wxWidgets 2.8.10:

73a74,77
>
> wxPanel* panel;
> wxSizer* sizer;
> wxStaticText* text;
174a179,186
>
> panel = new wxPanel(this);
> sizer = new wxBoxSizer(wxVERTICAL);
> text = new wxStaticText(panel, wxID_ANY, "Word\tword");
> sizer->Add(text);
> sizer->Layout();
> panel->SetSizer(sizer);
> sizer->SetSizeHints(panel);

displays only the first "Word". The second "word" is not displayed.

Regards,
Nate.
_________________________________________________________________
Eligible CDN College & University students can upgrade to Windows 7 before Jan 3 for only $39.99. Upgrade now!
http://go.microsoft.com/?linkid=9691819

Vadim Zeitlin

unread,
Dec 12, 2009, 5:32:55 AM12/12/09
to wx-u...@googlegroups.com
On Sat, 12 Dec 2009 02:40:54 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> > NR> I'm having an issue with wxStaticText on Windows where
NR> > NR> any text after a tab character is not displayed.
NR> >
NR> > How can this be reproduced?
NR>
NR> The following patch to minimal.cpp of wxWidgets 2.8.10:
NR>
NR> 73a74,77
NR> >
NR> > wxPanel* panel;
NR> > wxSizer* sizer;
NR> > wxStaticText* text;
NR> 174a179,186
NR> >
NR> > panel = new wxPanel(this);
NR> > sizer = new wxBoxSizer(wxVERTICAL);
NR> > text = new wxStaticText(panel, wxID_ANY, "Word\tword");
NR> > sizer->Add(text);
NR> > sizer->Layout();
NR> > panel->SetSizer(sizer);
NR> > sizer->SetSizeHints(panel);
NR>
NR> displays only the first "Word". The second "word" is not displayed.

Thanks, I can see and easily debug it now (which is why posting patches is
so useful -- just please make them unified ones if possible in the future).
The problem is due to a bug in wxDC::GetTextExtent() under MSW which
doesn't handle TABs correctly and the following patch, which should apply
to 2.8.10 too, should fix it:

diff --git a/src/msw/dc.cpp b/src/msw/dc.cpp
index 675447f..eedf917 100644
--- a/src/msw/dc.cpp
+++ b/src/msw/dc.cpp
@@ -1710,9 +1710,10 @@ wxCoord wxMSWDCImpl::GetCharWidth() const
return lpTextMetric.tmAveCharWidth;
}

-void wxMSWDCImpl::DoGetTextExtent(const wxString& string, wxCoord *x, wxCoord *y,
- wxCoord *descent, wxCoord *externalLeading,
- const wxFont *font) const
+void wxMSWDCImpl::DoGetTextExtent(const wxString& stringOrig,
+ wxCoord *x, wxCoord *y,
+ wxCoord *descent, wxCoord *externalLeading,
+ const wxFont *font) const
{
#ifdef __WXMICROWIN__
if (!GetHDC())
@@ -1737,6 +1738,14 @@ void wxMSWDCImpl::DoGetTextExtent(const wxString& string, wxCoord *x, wxCoord *y
hfontOld = 0;
}

+ // GetTextExtentPoint32() doesn't handle TABs correctly so expand them
+ // manually or the returned string would be too small
+ //
+ // notice that MSW allows changing the number of spaces per TAB (e.g. using
+ // DT_TABSTOP flag of DrawText()) but wx API does not so we can safely
+ // assume that TABs are always rendered as 8 spaces
+ wxString string(stringOrig);
+ string.Replace(wxT("\t"), wxT(" "));
SIZE sizeRect;
const size_t len = string.length();
if ( !::GetTextExtentPoint32(GetHdc(), string.wx_str(), len, &sizeRect) )

Please let me know if you still have any problems,

Nathan Ridge

unread,
Dec 12, 2009, 12:34:20 PM12/12/09
to wx-u...@googlegroups.com

> From: va...@wxwidgets.org
>
> Thanks, I can see and easily debug it now (which is why posting patches is
> so useful -- just please make them unified ones if possible in the future).
> The problem is due to a bug in wxDC::GetTextExtent() under MSW which
> doesn't handle TABs correctly and the following patch, which should apply
> to 2.8.10 too, should fix it:
>

I'm a bit new to patches, so please bear with me...

I'm having some trouble applying the patch.
I copied your patch to a text file patch.txt in the root folder
of wxWidgets, and in the same folder ran:
patch -p1 < patch.txt

but it's giving me an error:

patching file `src/msw/dc.cpp'
patch: **** malformed patch at line 5: return lpTextMetric.tmAveCharWidth;

Also, what do you mean by unified?

Thanks,

Vadim Zeitlin

unread,
Dec 12, 2009, 6:36:09 PM12/12/09
to wx-u...@googlegroups.com
On Sat, 12 Dec 2009 17:34:20 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> I'm a bit new to patches, so please bear with me...
NR>
NR> I'm having some trouble applying the patch.
NR> I copied your patch to a text file patch.txt in the root folder
NR> of wxWidgets, and in the same folder ran:
NR> patch -p1 < patch.txt
NR>
NR> but it's giving me an error:
NR>
NR> patching file `src/msw/dc.cpp'
NR> patch: **** malformed patch at line 5: return lpTextMetric.tmAveCharWidth;

Sorry, I don't know what could be wrong. You apply it exactly as it's
supposed to be done. Maybe it got mangled in transit? Let me attach it as a
file this time.

NR> Also, what do you mean by unified?

Done with "diff -u" (or "svn diff").

Regards,

log.diff

Nathan Ridge

unread,
Dec 13, 2009, 1:24:53 AM12/13/09
to wx-u...@googlegroups.com

> From: va...@wxwidgets.org
>
> On Sat, 12 Dec 2009 17:34:20 +0000 Nathan Ridge wrote:
>
> NR> I'm a bit new to patches, so please bear with me...
> NR>
> NR> I'm having some trouble applying the patch.
> NR> I copied your patch to a text file patch.txt in the root folder
> NR> of wxWidgets, and in the same folder ran:
> NR> patch -p1 < patch.txt
> NR>
> NR> but it's giving me an error:
> NR>
> NR> patching file `src/msw/dc.cpp'
> NR> patch: **** malformed patch at line 5: return lpTextMetric.tmAveCharWidth;
>
> Sorry, I don't know what could be wrong. You apply it exactly as it's
> supposed to be done. Maybe it got mangled in transit? Let me attach it as a
> file this time.

OK, I tried with the file, and this time it performed the patch,
but it still gave some errors:

patching file `src/msw/dc.cpp'
Hunk #1 FAILED at 1710.
Hunk #2 succeeded at 1763 with fuzz 1 (offset 25 lines).
1 out of 2 hunks FAILED -- saving rejects to src/msw/dc.cpp.rej

The contents of src/msw/dc.cpp.rej are the following:

***************
*** 1710,1718 ****
return lpTextMetric.tmAveCharWidth;
}

- void wxMSWDCImpl::DoGetTextExtent(const wxString& string, wxCoord *x, wxCoord *y,
- wxCoord *descent, wxCoord *externalLeading,
- const wxFont *font) const
{
#ifdef __WXMICROWIN__
if (!GetHDC())
--- 1710,1719 ----
return lpTextMetric.tmAveCharWidth;
}

+ void wxMSWDCImpl::DoGetTextExtent(const wxString& stringOrig,
+ wxCoord *x, wxCoord *y,
+ wxCoord *descent, wxCoord *externalLeading,
+ const wxFont *font) const
{
#ifdef __WXMICROWIN__
if (!GetHDC())


Clearly it did not apply the patch properly. Compiling dc.cpp
subsequently failed.

Any idea what could be the problem? If not, could you just send
the entire patched dc.cpp file? If you're worried about sending
large attachments to the list, you can send it to zerat...@hotmail.com
directly.

Vadim Zeitlin

unread,
Dec 13, 2009, 6:58:46 AM12/13/09
to wx-u...@googlegroups.com
On Sun, 13 Dec 2009 06:24:53 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> OK, I tried with the file, and this time it performed the patch,
NR> but it still gave some errors:


NR>
NR> patching file `src/msw/dc.cpp'

NR> Hunk #1 FAILED at 1710.
NR> Hunk #2 succeeded at 1763 with fuzz 1 (offset 25 lines).
NR> 1 out of 2 hunks FAILED -- saving rejects to src/msw/dc.cpp.rej

If you read the patch you can see that it just renames one of the function
parameters and adds an extra function call, so you can easily apply it
manually. The reason it doesn't apply automatically is that wxDC class had
been renamed/refactored to wxMSWDCImpl in the trunk (sorry for forgetting
about this...) and so it can't rename the parameter automatically.

Nathan Ridge

unread,
Dec 13, 2009, 2:37:57 PM12/13/09
to wx-u...@googlegroups.com

> From: va...@wxwidgets.org
>
> If you read the patch you can see that it just renames one of the function
> parameters and adds an extra function call, so you can easily apply it
> manually. The reason it doesn't apply automatically is that wxDC class had
> been renamed/refactored to wxMSWDCImpl in the trunk (sorry for forgetting
> about this...) and so it can't rename the parameter automatically.

OK, I was able to apply the patch manually.

However, it does not appear to solve the problem in general.
If I add a second "word" before the tab, the third "word"
now does not get displayed:

diff -u ../minimal_unmodified/minimal.cpp minimal.cpp
--- ../minimal_unmodified/minimal.cpp Mon Aug 6 17:43:52 2007
+++ minimal.cpp Sun Dec 13 14:32:57 2009
@@ -71,6 +71,10 @@
private:
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
+
+ wxPanel* panel;
+ wxSizer* sizer;
+ wxStaticText* text;
};

// ----------------------------------------------------------------------------
@@ -172,6 +176,14 @@
CreateStatusBar(2);
SetStatusText(_T("Welcome to wxWidgets!"));
#endif // wxUSE_STATUSBAR
+
+ panel = new wxPanel(this);
+ sizer = new wxBoxSizer(wxVERTICAL);
+ text = new wxStaticText(panel, wxID_ANY, "Word word\tword");
+ sizer->Add(text);
+ sizer->Layout();
+ panel->SetSizer(sizer);
+ sizer->SetSizeHints(panel);
}

Regards,
Nate.
_________________________________________________________________
Windows Live: Keep your friends up to date with what you do online.
http://go.microsoft.com/?linkid=9691815

Vadim Zeitlin

unread,
Dec 13, 2009, 7:14:32 PM12/13/09
to wx-u...@googlegroups.com
On Sun, 13 Dec 2009 19:37:57 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> However, it does not appear to solve the problem in general.
NR> If I add a second "word" before the tab, the third "word"
NR> now does not get displayed:

Oops, you're right, thanks for testing!

I naively thought Windows replaced TABs with 8 spaces but it doesn't do
this at all, it just happened to work with the test I used (which wasn't
even your one, I used something like "foo\tbar\tbaz" which also just
happened to work).

My next idea was that it replaced each TAB with the number of spaces up to
the next multiple of 8. But it doesn't do this neither, although this does
work for fixed width fonts.

In general however it seems to align the characters after TAB on the next
value multiple to 8*average_font_char_width in pixels. This does make sense
now that I think about it, as otherwise columns created using TABs would
not align properly. However it also means that my patch is totally wrong
and that this needs to be done in a quite different way: modifying the
input string won't do it, we need to call GetTextExtentPoint32() for each
segment between the TABs and then increase the width to the next tab stop.
I don't have time to do it now, but it shouldn't be difficult to modify
DoGetTextExtent() to do it. If you have a possibility to spend some time on
it, patches fixing the problem would be very welcome. Otherwise please open
a ticket on Trac so that we don't forget to fix it later.

Thanks,

Nathan Ridge

unread,
Dec 14, 2009, 1:35:40 AM12/14/09
to wx-u...@googlegroups.com

----------------------------------------
> From: va...@wxwidgets.org
>
> NR> However, it does not appear to solve the problem in general.
> NR> If I add a second "word" before the tab, the third "word"
> NR> now does not get displayed:
>
> Oops, you're right, thanks for testing!
>
> I naively thought Windows replaced TABs with 8 spaces but it doesn't do
> this at all, it just happened to work with the test I used (which wasn't
> even your one, I used something like "foo\tbar\tbaz" which also just
> happened to work).
>
> My next idea was that it replaced each TAB with the number of spaces up to
> the next multiple of 8. But it doesn't do this neither, although this does
> work for fixed width fonts.
>
> In general however it seems to align the characters after TAB on the next
> value multiple to 8*average_font_char_width in pixels. This does make sense
> now that I think about it, as otherwise columns created using TABs would
> not align properly. However it also means that my patch is totally wrong
> and that this needs to be done in a quite different way: modifying the
> input string won't do it, we need to call GetTextExtentPoint32() for each
> segment between the TABs and then increase the width to the next tab stop.

Perhaps we can use GetTabbedTextExtent() instead of GetTextExtentPoint32()?

The MSDN documentation for GetTabbedTextExtent
(http://msdn.microsoft.com/en-us/library/dd144930(VS.85).aspx) says:

"If the nTabPositions parameter is zero and the lpnTabStopPositions parameter is NULL, tabs are expanded to eight times the average character width."

which seems to be the behaviour we want.

Nathan Ridge

unread,
Dec 14, 2009, 2:36:27 AM12/14/09
to wx-u...@googlegroups.com


----------------------------------------
> From: zerat...@hotmail.com
So I tried changing wxDC::DoGetTextExtent() to use GetTabbedTextExtent()
instead of GetTextExtentPoint32(). Here is a patch containing
the changes I made to dc.cpp (note: the changes are relative to the
original version of dc.cpp, not the one with Vadim's patch applied).

diff -u -b dc.cpp.orig dc.cpp
--- dc.cpp.orig Fri Nov 9 15:16:00 2007
+++ dc.cpp Mon Dec 14 02:32:50 2009
@@ -1762,12 +1762,17 @@
hfontOld = 0;
}

- SIZE sizeRect;
+ // Note: GetTabbedTextExtent() is used instead of GetTextExtentPoint32()
+ // because the latter does not account properly for tabs.
const size_t len = string.length();
- if ( !::GetTextExtentPoint32(GetHdc(), string, len, &sizeRect) )
+ DWORD dwSize = ::GetTabbedTextExtent(GetHdc(), string, len, 0, NULL);
+ if ( dwSize == 0 )
{
- wxLogLastError(_T("GetTextExtentPoint32()"));
+ wxLogLastError(_T("GetTabbedTextExtent()"));
}
+ SIZE sizeRect;
+ sizeRect.cx = LOWORD(dwSize);
+ sizeRect.cy = HIWORD(dwSize);

#if !defined(_WIN32_WCE) || (_WIN32_WCE>= 400)
// the result computed by GetTextExtentPoint32() may be too small as it


However, this still does not solve the problem in general.
Now both "Word\tword" and "Word word\tword" display correctly,
but for example "Word da doo da doo dam\tword" does not
(using the same patch to the minimal sample as before).

Any ideas on what might still be going wrong?

Regards,

Vadim Zeitlin

unread,
Dec 14, 2009, 8:24:26 AM12/14/09
to wx-u...@googlegroups.com
On Mon, 14 Dec 2009 06:35:40 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> Perhaps we can use GetTabbedTextExtent() instead of GetTextExtentPoint32()?

This looks like exactly the right function to use, I simply didn't know
about it, thanks!

Just to play a devil advocate it could be argued that maybe wxDC doesn't
need to use it as, after all, it doesn't use TabbedTextOut() in its
DrawText(). Maybe it would be better to provide separate function for text
with TABs just as MSW itself does (I'd prefer to append a flags parameter
to GetTextExtent() but this would require changing all derived wxDC
classes). Of course, this objection has nothing to do with whether we use
GetTabbedTextExtent() or the manual solution I initially thought of, it's
just that now that I see that MSW has different functions for normal and
tabbed text I start thinking that it might be a bad idea to always use the
tabbed versions.

NR> So I tried changing wxDC::DoGetTextExtent() to use GetTabbedTextExtent()
NR> instead of GetTextExtentPoint32(). Here is a patch containing
NR> the changes I made to dc.cpp (note: the changes are relative to the
NR> original version of dc.cpp, not the one with Vadim's patch applied).
NR>
NR> diff -u -b dc.cpp.orig dc.cpp
NR> --- dc.cpp.orig Fri Nov 9 15:16:00 2007
NR> +++ dc.cpp Mon Dec 14 02:32:50 2009
NR> @@ -1762,12 +1762,17 @@
NR> hfontOld = 0;
NR> }
NR>
NR> - SIZE sizeRect;
NR> + // Note: GetTabbedTextExtent() is used instead of GetTextExtentPoint32()
NR> + // because the latter does not account properly for tabs.
NR> const size_t len = string.length();
NR> - if ( !::GetTextExtentPoint32(GetHdc(), string, len, &sizeRect) )
NR> + DWORD dwSize = ::GetTabbedTextExtent(GetHdc(), string, len, 0, NULL);
NR> + if ( dwSize == 0 )
NR> {
NR> - wxLogLastError(_T("GetTextExtentPoint32()"));
NR> + wxLogLastError(_T("GetTabbedTextExtent()"));
NR> }
NR> + SIZE sizeRect;
NR> + sizeRect.cx = LOWORD(dwSize);
NR> + sizeRect.cy = HIWORD(dwSize);
NR>
NR> #if !defined(_WIN32_WCE) || (_WIN32_WCE>= 400)
NR> // the result computed by GetTextExtentPoint32() may be too small as it
NR>
NR>
NR> However, this still does not solve the problem in general.
NR> Now both "Word\tword" and "Word word\tword" display correctly,
NR> but for example "Word da doo da doo dam\tword" does not
NR> (using the same patch to the minimal sample as before).
NR>
NR> Any ideas on what might still be going wrong?

Not really, your code looks exactly as I would have tried to do it myself.
All I can offer is a patch for testing this function which I found
convenient to use as using wxBORDER_SIMPLE allows to see the computed
control size visually:

diff --git a/samples/minimal/minimal.cpp b/samples/minimal/minimal.cpp
index 80bf766..ed995f6 100644
--- a/samples/minimal/minimal.cpp
+++ b/samples/minimal/minimal.cpp
@@ -172,6 +172,31 @@ MyFrame::MyFrame(const wxString& title)


CreateStatusBar(2);
SetStatusText("Welcome to wxWidgets!");

#endif // wxUSE_STATUSBAR
+
+ class MyText : public wxStaticText
+ {
+ public:
+ MyText(wxWindow *parent, const char *label)
+ : wxStaticText(parent, wxID_ANY, label,
+ wxDefaultPosition, wxDefaultSize,
+ wxBORDER_SIMPLE)
+ {
+ }
+ };
+
+ //SetFont(wxFont(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
+ wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(new MyText(this, "0123456789012345678901234567890123456789"));
+ sizer->Add(new MyText(this, "Word\tword "));
+ sizer->Add(new MyText(this, "mmm\tword "));
+ sizer->Add(new MyText(this, "MM\tword "));
+ sizer->Add(new MyText(this, "Word word"));
+ sizer->Add(new MyText(this, "Word word\tword "));
+ sizer->Add(new MyText(this, "Word word word"));
+ sizer->Add(new MyText(this, "Word word another one\tword "));
+ sizer->Add(new MyText(this, "Word word another one word"));
+ sizer->Add(new MyText(this, "0123456789012345678901234567890123456789"));
+ SetSizerAndFit(sizer);
}


Sorry for lack of any ideas...

Nathan Ridge

unread,
Dec 14, 2009, 5:52:00 PM12/14/09
to wx-u...@googlegroups.com

> From: va...@wxwidgets.org
> Subject: Re[11]: wxStaticText stops displaying text after tab character
> To: wx-u...@googlegroups.com
>
> On Mon, 14 Dec 2009 06:35:40 +0000 Nathan Ridge wrote:
>
> NR> Perhaps we can use GetTabbedTextExtent() instead of GetTextExtentPoint32()?
>
> This looks like exactly the right function to use, I simply didn't know
> about it, thanks!
>
> Just to play a devil advocate it could be argued that maybe wxDC doesn't
> need to use it as, after all, it doesn't use TabbedTextOut() in its
> DrawText(). Maybe it would be better to provide separate function for text
> with TABs just as MSW itself does (I'd prefer to append a flags parameter
> to GetTextExtent() but this would require changing all derived wxDC
> classes). Of course, this objection has nothing to do with whether we use
> GetTabbedTextExtent() or the manual solution I initially thought of, it's
> just that now that I see that MSW has different functions for normal and
> tabbed text I start thinking that it might be a bad idea to always use the
> tabbed versions.
>
> <snip>
>
> All I can offer is a patch for testing this function which I found
> convenient to use as using wxBORDER_SIMPLE allows to see the computed
> control size visually:
>

Thanks for that patch, it made debugging this much easier!

It turns out GetTabbedTextExtent() barely ever returns the correct
length for strings with tabs in them. Sometimes it leaves extra space
at the end of the string, and sometimes it cuts the string off.

The reason for all this, it seems, is that the tab spacing used
by GetTabbedTextExtent() is not the same as the one used by TextOut().
This is evident when you display "\t" and "\ta" one underneath
the other. The box around the "\t" is wider than the amount of space
actually taken up by the tab in "\ta", indicating that the tab spacing
used by GetTabbedTextExtent() to compute the length of "\t"
is larger than the tab spacing used by TextOut() to display a tab
at the beginning of a line.

I tried changing wxDC::DrawText() to use TabbedTextOut() instead of
TextOut(), but the results were identical: there appears to be
no difference between the spacing used by TextOut() and TabbedTextOut().

I was not able to figure out how to get GetTabbedTextExtent()
to use the same tab spacing as TextOut(), so I implemented
your suggested manual solution. Here's the patch against
the original dc.cpp:

diff -u -b dc.cpp.orig dc.cpp
--- dc.cpp.orig Mon Dec 14 16:42:01 2009
+++ dc.cpp Mon Dec 14 17:45:25 2009
@@ -42,6 +42,7 @@

#include "wx/sysopt.h"
#include "wx/dynlib.h"
+#include "wx/tokenzr.h"

#ifdef wxHAVE_RAW_BITMAP
#include "wx/rawbmp.h"
@@ -1769,6 +1770,61 @@
wxLogLastError(_T("GetTextExtentPoint32()"));
}

+ // The width of strings containing tabs needs to be treated specially
+ // because GetTextExtentPoint32() does not handle tabs correctly.
+ if ( string.Find('\t') != wxNOT_FOUND )
+ {
+ // Note: while there is a GetTabbedTextExtent() API, it does
+ // not appear to handle tabs correctly either, so we need to do
+ // some manual computation.
+
+ // Compute the average character width.
+ // Tab stop positions are multiples of 8 of this.
+ // Note: this computation cannot be done just once in advance because
+ // the font may change, giving a different answer.
+ static wxString allChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const size_t allCharsLen = allChars.length();
+ SIZE allCharsSizeRect;
+ if ( !::GetTextExtentPoint32(GetHdc(), allChars, allCharsLen,
+ &allCharsSizeRect) )
+ {
+ wxLogLastError(_T("GetTextExtentPoint32()"));
+ }
+ DWORD avgCharWidth = allCharsSizeRect.cx / allCharsLen;
+ DWORD tabWidth = avgCharWidth * 8;
+
+ // Split the string into chunks separated by tabs,
+ // and add up the widths the chunks plus the tabs
+ // betweent them.
+ // Note: we use wxTOKEN_RET_EMPTY_ALL while tokenizing so that
+ // multiple consecutive tabs and trailing tabs are handled correctly.
+ DWORD width = 0;
+ wxStringTokenizer tokenizer(string, '\t', wxTOKEN_RET_EMPTY_ALL);
+ while ( tokenizer.HasMoreTokens() )
+ {
+ // Compute width of token.
+ SIZE tokenSizeRect;
+ wxString token = tokenizer.GetNextToken();
+ const size_t tokenLen = token.length();
+ if ( !::GetTextExtentPoint32(GetHdc(), token, tokenLen,
+ &tokenSizeRect) )
+ {
+ wxLogLastError(_T("GetTextExtentPoint32()"));
+ }
+ width += tokenSizeRect.cx;
+
+ // If there are more tokens, there is a tab between this
+ // token and the next. Compute its length.
+ if ( tokenizer.HasMoreTokens() )
+ width = ((width / tabWidth) + 1) * tabWidth;
+ }
+
+ // Overwrite value computed by GetTextExtentPoint32() with the
+ // correct value.
+ sizeRect.cx = width;
+ }
+
#if !defined(_WIN32_WCE) || (_WIN32_WCE>= 400)
// the result computed by GetTextExtentPoint32() may be too small as it
// accounts for under/overhang of the first/last character while we want


I've tried this on a variety of strings, including on strings with
multiple consecutive tabs and trailing tabs. It works correctly
for every string I tried. Also, it does not do any extra work
for strings that do not contain tabs.

If someone can figure out how to use GetTabbedTextExtent() properly,
that would obviously be a better solution, but until then this manual
solution is the best I have.

Let me know if I still should open a Trac ticket about this issue.

iko...@earthlink.net

unread,
Dec 14, 2009, 7:33:16 PM12/14/09
to wx-u...@googlegroups.com
Hi, (Nathan, Vadim),
Maybe it's not even possible to do this?
Here is an except from the VC++5 help:

"Because some devices do not place characters in regular cell arrays
(that is, they kern the characters), the sum of the extents of the
characters in a string may not be equal to the extent of the string."

I'm assuming that in this case the function won't fail, but just produce
incorrect results.

Thank you.

>
>Let me know if I still should open a Trac ticket about this issue.
>
>Regards,
>Nate.
>
>_________________________________________________________________
>Eligible CDN College & University students can upgrade to Windows 7 before Jan 3 for only $39.99. Upgrade now!
>http://go.microsoft.com/?linkid=9691819
>
>--
>Please read http://www.wxwidgets.org/support/mlhowto.htm before posting.
>
>To unsubscribe, send email to wx-users+u...@googlegroups.com
>or visit http://groups.google.com/group/wx-users

Nathan Ridge

unread,
Dec 14, 2009, 8:36:26 PM12/14/09
to wx-u...@googlegroups.com

> From: iko...@earthlink.net
>
> Hi, (Nathan, Vadim),
>
>>From: Nathan Ridge
>>
>> <snip>
My understanding is that the point of kerning is to adjust the spacing
between consecutive characters to make the text look more natural.
I don't see how it would make sense to have any "kerning" between
visible characters and tabs. Since I only split the string at
visible character-tab boundaries, I don't think kerning will be a problem.

I could be wrong, of course, but I haven't come across a string for which
this modified version of DoGetTextExtent() returns an incorrect result,
not even off by a few pixels.

Vadim Zeitlin

unread,
Jan 9, 2010, 1:01:36 PM1/9/10
to wx-u...@googlegroups.com
[sorry for the late reply... but better late than never]

On Mon, 14 Dec 2009 22:52:00 +0000 Nathan Ridge <zerat...@hotmail.com> wrote:

NR> It turns out GetTabbedTextExtent() barely ever returns the correct
NR> length for strings with tabs in them. Sometimes it leaves extra space
NR> at the end of the string, and sometimes it cuts the string off.

I still can't understand what is this function for if it can't be used
for, you know, the extend of tabbed text. So I still suspect there is
something we don't understand but even after looking at it anew I don't see
what could we be missing.

NR> I was not able to figure out how to get GetTabbedTextExtent()
NR> to use the same tab spacing as TextOut(), so I implemented
NR> your suggested manual solution. Here's the patch against
NR> the original dc.cpp:
...
NR> I've tried this on a variety of strings, including on strings with
NR> multiple consecutive tabs and trailing tabs. It works correctly
NR> for every string I tried. Also, it does not do any extra work
NR> for strings that do not contain tabs.

Thanks a lot for this patch, I guess we should just apply it. But I'd like
to look at this for one last time (to be honest, I want to try stepping
into GetTabbedTextExtent() to see what exactly does it do -- but this
requires more time than I have now) so could you please submit this patch
to the Trac so that it doesn't get lost until then?

NR> Let me know if I still should open a Trac ticket about this issue.

Please do it if possible.

Thanks,

Reply all
Reply to author
Forward
0 new messages