Ask The Experts: Tab Stop Size

Talk about anything
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Ask The Experts: Tab Stop Size

29 Aug 2017, 18:29

Preface
This is problem that I've been working on for several years now. It goes in cycles. I come across a need to figure it out. I search on Google. I find nothing. I search on Bing. I find nothing. I update my search and try again. Nothing. I give up in frustration. Repeat a few weeks or few months later.

The Requirement/Problem
I need to figure out how to calculate the size of a tab stop for a particular GUI control.

For a few GUI controls like the Edit and ListBox controls, the default tab stop size is known. It's the width of 8 average characters. In this instance, "average characters" is also known as "dialog base units". 8 dialog base units is equal to 32 dialog template units. These units are known and can be calculated.

For other GUI controls like the Text control, the default (and only) tab stop size appears to be dynamic, depending on the font. For some fonts, the tab stop size is ~32 dialog template units, for other fonts, it's 24, or 20, or... it can be anything. I'm not even sure the tab stop size for these controls is measured in dialog template units.

Obviously, Microsoft knows how to calculate this information, but I haven't been able to find any documentation on how the tab stop size is calculated for these GUI controls. I recently figured out how to get this information by measuring it but it would be nice to be able to calculate it.

Any help would be appreciated.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

29 Aug 2017, 20:16

One possibility would be to calculate the text size for strings experimenting with different flags for DrawText/DrawTextEx.

Code: Select all

;DT_EDITCONTROL := 0x2000
;DT_NOPREFIX := 0x800 ;ampersands
;DT_CALCRECT := 0x400
;DT_NOCLIP := 0x100
;DT_TABSTOP := 0x80
;DT_EXPANDTABS := 0x40
;DT_SINGLELINE := 0x20
;DT_WORDBREAK := 0x10
You could also check the control window styles/ex. styles for any styles relevant to tabs.

Do you have any code for this? This is what I have so far, tested on an Edit control. Although like you say, Edit controls aren't the problem.

Code: Select all

q:: ;control get font, font get average character width
SendMessage, 0x31, 0, 0, Edit1, ahk_class Notepad ;WM_GETFONT := 0x31
hFont := ErrorLevel
hFont2 := JEE_FontClone(hFont)
MsgBox, % JEE_StrGetDimAvgCharWidth(hFont2)*8
return

;iTabLength
;The size of each tab stop, in units equal to the average character width.
;(a tab character is 8 times wider than the average character width)

;==================================================

;e.g. hFontNew := JEE_FontClone(hFont)
JEE_FontClone(hFont)
{
	VarSetCapacity(LOGFONT, 92, 0) ;LOGFONT (Unicode)
	vSize := DllCall("gdi32\GetObject", Ptr,hFont, Int,0, Ptr,0)
	DllCall("gdi32\GetObject", Ptr,hFont, Int,vSize, Ptr,&LOGFONT)
	return DllCall("CreateFontIndirect", Ptr,&LOGFONT, Ptr)
}

;==================================================

;average width of a character in pixels
JEE_StrGetDimAvgCharWidth(hFont)
{
	vText := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	hDC := DllCall("user32\GetDC", Ptr,0, Ptr)
	hFontOld := DllCall("gdi32\SelectObject", Ptr,hDC, Ptr,hFont, Ptr)
	;HWND_DESKTOP := 0
	VarSetCapacity(SIZE, 8, 0)
	DllCall("gdi32\GetTextExtentPoint32", Ptr,hDC, Str,vText, Int,StrLen(vText), Ptr,&SIZE)
	DllCall("gdi32\SelectObject", Ptr,hDC, Ptr,hFontOld, Ptr)
	DllCall("user32\ReleaseDC", Ptr,0, Ptr,hDC)
	vTextW1 := NumGet(SIZE, 0, "Int") ;cx ;logical units
	vTextW2 := Floor((vTextW1/26+1)/2)
	return vTextW2
}

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Ask The Experts: Tab Stop Size

29 Aug 2017, 21:16

jeeswg, Thank you for your response.

Yes, DrawText/DrawTextEx is how the default tab stop size for most controls can be measured. Set the string to to a single tab character (Ex: "`t"). For the Edit and ListBox controls, use the DT_CALCRECT, DT_EXPANDTABS, and DT_EDITCONTROL flags. For a Text and similar controls, use only the DT_CALCRECT and DT_EXPANDTABS flags.

Like you said, there is no need to measure for the Edit, ListBox, etc. controls because the default tab stop can be calculated. As in your example, your JEE_StrGetDimAvgCharWidth function (similar to the Fnt_GetDialogBaseUnits function in the Fnt library) multiplied by 8 will give you the default tab stop size for these controls.

What's missing is a way to calculate the default tab stop for controls that are not Edit, ListBox, etc. Why Microsoft decided to have different default tab stop sizes for different GUI controls, I'll never know. Mine is not to ask why, but I still would like to know how to calculate it doggone it! I apologize for the rough language.

BTW, your JEE_FontClone looks useful. With your permission, I may incorporate it into the Fnt library.

Once again, thank you for your response. If you have any other ideas, make a noise!
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

29 Aug 2017, 21:41

Yes you can incorporate it, drop the 'JEE_' and change it, including the function name if you want to, as much or as little as you want, so that it matches the style of the other functions in the library.

It would be helpful if you could include a bit more info. Which are the 'good' controls and which are the 'bad' controls, do you get bad behaviour even when you create the controls yourself in AutoHotkey, and play about with the window styles/ex. styles. And then take an example font, and an example control type, and list the tab widths for different sizes, compared to the tab widths in a 'good' control.

Some other considerations are to try out fonts like Fixedsys or Courier New, i.e. odd fonts or fonts that are equal width (i.e. every character has the same width), and check whether in those fonts, 8 characters and 1 tab have the same with. Another thing for testing is to do something like 'a tab a' and subtract the width of 'a' from the total, to try and get the width of a tab.

I.e. a good start would be to numerically quantify the differences between 'good' and 'bad' controls.

Is the behaviour consistent in 'bad' controls, do we simply have two types of behaviour?
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 00:34

jeeswg wrote:...do we simply have two types of behaviour?
Yes, I'm pretty sure this is the case.

First, there are the GUI controls that use 32 dialog template units (i.e. 8 average characters) as the default tab stop size. This includes the Edit, ListBox, RichEdit (I think but I haven't tested it) and a few others. Then there are all the other GUI controls that support text. The best example of an "other" GUI control is the Text control. Of course I haven't tested every last GUI control but I have tested most of the GUI controls supported by AutoHotkey.

When using the "DrawText" function, the DT_EXPANDTABS format instructs the program to calculate the tab size when a tab character is found in the string. If the DT_EDITCONTROL format is also included, the program uses 8 average characters as the default tab stop size. If the DT_EDITCONTROL format is not included, the program uses another font-based calculation to determine the tab stop size. It's this "other font-based calculation" that I'm looking for.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 02:51

I have an interesting little demo script:

Code: Select all

DetectHiddenWindows, On
Gui, New, +HwndhGui, MyWinTitle
Gui, Add, Text, w400 x10, abcde`tabcde`tabcde`tabcde`tabcde
Gui, Add, Edit, w400 x7 t20 r2, abcde`tabcde`tabcde`tabcde`tabcde
;SS_EDITCONTROL := 0x2000
Gui, Add, Text, w400 x10 +0x2000, abcde`tabcde`tabcde`tabcde`tabcde
Gui, Add, Edit, w400 x7, abcde`tabcde`tabcde`tabcde`tabcde

Gui, Font,, Courier New
Gui, Add, Text, w400 x10, abcde`tabcde`tabcde`tabcde`tabcde
Gui, Add, Edit, w400 x7 t20 r2, abcde`tabcde`tabcde`tabcde`tabcde
;SS_EDITCONTROL := 0x2000
Gui, Add, Text, w400 x10 +0x2000, abcde`tabcde`tabcde`tabcde`tabcde
Gui, Add, Edit, w400 x7, abcde`tabcde`tabcde`tabcde`tabcde

Gui, Show
Sleep, 3000
VarSetCapacity(vData, 4, 0)
NumPut(64, vData, 0, "UInt")
SendMessage, 0xCB, 1, % &vData, Edit1, % "ahk_id " hGui ;EM_SETTABSTOPS := 0xCB
SendMessage, 0xCB, 1, % &vData, Edit3, % "ahk_id " hGui ;EM_SETTABSTOPS := 0xCB
return
It appears that Static controls (what AHK calls 'Text' or 'Picture' controls) set the tabstops to 20 units, whereas Edit controls set them to 32 units. However, for Courier New (an equal-width font), tabstops seem to be 32 units for both Static and Edit.

One thing I find confusing is that the word 'tabstops' also seems to be used to refer to controls that are navigated to when you press the tab key e.g. WS_TABSTOP.

SS_EDITCONTROL makes Static controls behave like Edit controls re. tabstops.

This looks quite interesting:
EM_SETTABSTOPS message (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
Setting Tab stops in edit controls - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/1023 ... -controls/

But then again, it may be that I haven't said more beyond what you've said, you've suggested it could be tabstops but smaller gaps, and that it varies per font, and that's what my tests show. Does that then mean there's some value inside the font itself relating to tabstops? A property of the font?

[EDIT:] I was wondering if maybe something like LaTeX might be a source of information on tabstop standards.

Also, perhaps they are using one particular character as the basis of the unit, a character that is typically smaller than the character average, but for an equal-width font, the same as the average.

Some candidates:
CSS Units
https://www.w3schools.com/cssref/css_units.asp

[EDIT:] The width of the character '0' didn't seem to work, but the width of 'x' seemed to be a good match, e.g. 8 x's = 1 tab.

Did you consider trying on Stack Overflow or some other forum?

[EDIT:] The en dash could be a good candidate also.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 03:57

Hmm, running this script with different fonts, to see if a particular character is an eighth of a tab in width, ultimately didn't find a match.
There is the possibility instead that it's based on height e.g. x-height, a proportion of some character's width e.g. 0-width, or something entirely different.

Code: Select all

#SingleInstance force
DetectHiddenWindows, On
Gui, New, +HwndhGui, MyWinTitle
vSize := "s10"
vNum := 16
;vNum := 8
Gui, Font, % vSize, Arial
;Gui, Font, % vSize, Courier New
;Gui, Font, % vSize, Times New Roman
vText := ""
Loop, % vNum
	vText .= "abcde`t"
vText2 := vText "_"
vText .= "_`r`n"
vList := "x,z,s,y," Chr(8211)
Loop, Parse, vList, % ","
	vText .= JEE_StrRept(A_LoopField, vNum*8) "`r`n"
Loop, 26
	vText .= JEE_StrRept(Chr(96+A_Index), vNum*8) "`r`n"
Gui, Add, Text, w1000 x10, % vText2
Gui, Add, Edit, w1000 x7 t20 r20, % vText
Gui, Show
return

JEE_StrRept(vText, vNum)
{
	return StrReplace(Format("{:" vNum "}","")," ",vText)
	;return StrReplace(Format("{:0" vNum "}",0),0,vText)
}

GuiClose:
;MsgBox, % "closing"
ExitApp
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 04:27

I've got what may be an answer:
These values, worked for Arial, but not Courier New or Times New Roman.
Note: the widths are not increasing by exactly 4 each time, see the font size 28 to 30 gap. However, they are increasing by multiples of 4 each time.

Code: Select all

;vList = font name,font size,tabstop width
vList = Arial,10,24
vList = Arial,12,28
vList = Arial,14,32
vList = Arial,16,36
vList = Arial,18,44
vList = Arial,20,48
vList = Arial,22,52
vList = Arial,24,56
vList = Arial,26,60
vList = Arial,28,64
vList = Arial,30,72
vList = Arial,32,76
[EDIT:] Here's an example script.
So what may be happening is that there's a simple familiar calculation, to work out the required tab width, that is then rounded (up?) to the nearest 4.

Code: Select all

#SingleInstance force

;font size - tabstop width
vFont := "Arial"
vFont := "Courier New"
vFont := "Times New Roman"
vSize := 10
vNum := 8

if (vFont = "Arial")
vList := " ;continuation section
(
10,24
12,28
14,32
16,36
18,44
20,48
22,52
24,56
26,60
28,64
30,72
32,76
)"

if (vFont = "Courier New")
vList := " ;continuation section
(
10,32
12,40
14,44
16,52
18,56
20,64
22,68
24,76
26,84
28,88
30,96
32,104
)"

if (vFont = "Times New Roman")
vList := " ;continuation section
(
10,20
12,24
14,32
16,32
18,40
20,44
22,48
24,52
26,56
28,60
30,64
32,68
)"

oArray := Object(StrSplit(vList, [",","`n"])*)
vTabstopW := oArray[vSize]

DetectHiddenWindows, On
Gui, New, +HwndhGui, MyWinTitle
Gui, Font, % "s" vSize, % vFont
vText := ""
Loop, % vNum
	vText .= "abcde`t"
vText .= "_"
Gui, Add, Text, w1000 x10, % vText
Gui, Add, Edit, % "w1000 x5 t" vTabstopW " r20", % vText
Gui, Show
return

GuiClose:
ExitApp
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 13:10

The values I have in this list are font size, tapstop size and JEE_StrGetDimAvgCharWidth.
For Courier New, the relationship seemed to be quite simple, tabstop size is 4 times the average character width, but it was not so simple for Arial. So the suggestion is that there is a different average width being used, which you then multiply by 4.

Code: Select all

if (vFont = "Arial")
vList := " ;continuation section
(C
10,24 ;7
12,28 ;9
14,32 ;11
16,36 ;12
18,44 ;14
20,48 ;16
22,52 ;17
24,56 ;18
26,60 ;20
28,64 ;22
30,72 ;23
32,76 ;25
)"

if (vFont = "Courier New")
vList := " ;continuation section
(C
10,32 ;8
12,40 ;10
14,44 ;11
16,52 ;13
18,56 ;14
20,64 ;16
22,68 ;17
24,76 ;19
26,84 ;21
28,88 ;22
30,96 ;24
32,104 ;26
)"

if (vFont = "Times New Roman")
vList := " ;continuation section
(C
10,20 ;7
12,24 ;9
14,32 ;10
16,32 ;12
18,40 ;13
20,44 ;15
22,48 ;16
24,52 ;18
26,56 ;19
28,60 ;21
30,64 ;22
32,68 ;24
)"
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 16:44

jeeswg, Thanks for the feedback. It's going to take some time to go through all of the stuff you posted.

There is a cigar out there, it just hasn't made an appearance. Just as the default tab stop size for the Edit control is based on a font-based calculation, I'm almost certain that the default tab stop size for "other" GUI controls like the Text control is a font-based calculation. It's just not based on dialog base units, aka "average character" size.

It's unfortunate but it appears that reverse engineering is not the way to go. I spent a fair share of time trying to figure out the calculation. I would get close but every time I changed the font, it would be off by a 1 pixel or 2 or 10. And no, we can't force everyone to only use Arial.

The answer is out there, I just haven't found it yet. If we had the source code for the DrawText function, this would be over (assuming that it is in a readable/understandable format).

Something that I just thought of... Anyone know of a way to ask a Microsoft expert without having to purchase a service contract or paying a fee? Or maybe posting it on a Microsoft or some other appropriate programming forum. Anyone know of any good ones?
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ask The Experts: Tab Stop Size

30 Aug 2017, 17:19

This is giving me the right answers:

Code: Select all

q::
vFontName := "Arial"
vFontName := "Courier New"
vFontName := "Times New Roman"

vOutput := ""
Loop, 12
{
	vSize := 8 + (A_Index*2)
	hFont := JEE_FontCreate(vFontName, vSize)

	;HWND_DESKTOP := 0
	hDC := DllCall("user32\GetDC", Ptr,0, Ptr)
	hFontOld := DllCall("gdi32\SelectObject", Ptr,hDC, Ptr,hFont, Ptr)
	VarSetCapacity(TEXTMETRIC, 60, 0)
	DllCall("gdi32\GetTextMetrics", Ptr,hDC, Ptr,&TEXTMETRIC)
	vAveCharWidth := NumGet(TEXTMETRIC, 20, "Int") ;tmAveCharWidth
	DllCall("gdi32\SelectObject", Ptr,hDC, Ptr,hFontOld, Ptr)
	DllCall("user32\ReleaseDC", Ptr,0, Ptr,hDC)
	vOutput .= vSize " " (vAveCharWidth*4) "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==================================================

;e.g. hFont := JEE_FontCreate("Arial", 12, "bius")

;JEE_CreateFont
JEE_FontCreate(vName, vSize, vFontStyle:="", vWeight:="")
{
	vHeight := -DllCall("kernel32\MulDiv", Int,vSize, Int,A_ScreenDPI, Int,72)
	vWidth := 0
	vEscapement := 0
	vOrientation := 0
	vWeight := (InStr(vFontStyle, "b") && (vWeight="")) ? 700 : 400
	vItalic := InStr(vFontStyle, "i") ? 1 : 0
	vUnderline := InStr(vFontStyle, "u") ? 1 : 0
	vStrikeOut := InStr(vFontStyle, "s") ? 1 : 0
	vCharSet := 0
	vOutPrecision := 0
	vClipPrecision := 0
	vQuality := 0
	vPitchAndFamily := 0
	vFaceName := vName
	vOutPrecision := 3
	vClipPrecision := 2
	vQuality := 1
	vPitchAndFamily := 34
	return DllCall("CreateFont", Int,vHeight, Int,vWidth, Int,vEscapement, Int,vOrientation, Int,vWeight, UInt,vItalic, UInt,vUnderline, UInt,vStrikeOut, UInt,vCharSet, UInt,vOutPrecision, UInt,vClipPrecision, UInt,vQuality, UInt,vPitchAndFamily, Str,vFaceName, Ptr)
}
TEXTMETRIC structure (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
The average width of characters in the font (generally defined as the width of the letter x ). This value does not include the overhang required for bold or italic characters.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Ask The Experts: Tab Stop Size

31 Aug 2017, 03:33

jeeswg,

If the font's average character width is multiplied by 8 instead of 4, we have a winner! Excellent work! :bravo: Now that I see it, it kinda makes sense. I can't believe that I didn't think to even try this calculation after all of this time. They say hindsight is 20/20.

I've written some fairly extensive tests to check this calculation and so far, almost everything I've tested seems to work. There is one anomaly so far but I need to do a bit more testing before calling it a done deal.

Thanks for the assist. It is appreciated. :)
User avatar
jballi
Posts: 724
Joined: 29 Sep 2013, 17:34

Re: Ask The Experts: Tab Stop Size

29 Sep 2017, 19:12

I finally got around to writing a follow-up to this topic. Hopefully it will be useful to someone. You can find the post here.

Return to “Off-topic Discussion”

Who is online

Users browsing this forum: No registered users and 143 guests