Font size for a string limited to two lines in length?

Get help with using AutoHotkey and its commands and hotkeys
User avatar
PuzzledGreatly
Posts: 1103
Joined: 29 Sep 2013, 22:18

Font size for a string limited to two lines in length?

Post by PuzzledGreatly » 13 May 2021, 08:23

I want to find the largest font size for any string that will fit in a given area but wrapping to no more than two lines in length. I tried this:

Code: Select all

GetFontSizeForTwoLines(t,w,h, fontname := "Segoe UI")
{
	static i,n,nf,nh,nw,v,wh
	
	i := 200
	loop
	{
		nf := Ceil(i * 0.9)
		Gui, dia:new, -dpiscale
		Gui, margin, 0, 0
		Gui, Font, s%i% bold, %fontname%
		Gui, add,text, vv, height
		Gui, add,text, w%w%  vn, %t%
		Guicontrolget, v, dia:pos
		Guicontrolget, n, dia:pos
		if (nh < h)
		break
		i:= nf 
	}
	msgbox, 4096, lines, % round(nh/vh,2)
	Return i
}
The msgbox gives me a result of 2.00 for my text but in my gui I'm seeing three lines rather than two. Can anyone think of a better approach? Thanks.
User avatar
Onimuru
Posts: 101
Joined: 08 Sep 2018, 18:35
GitHub: Onimuru

Re: Font size for a string limited to two lines in length?

Post by Onimuru » 13 May 2021, 11:40

You can use GDIp to good effect for this:

Code: Select all

;* Startup:
DllCall("Kernel32\LoadLibrary", "Str", "Gdiplus")

VarSetCapacity(input, 8 + A_PtrSize*2)
	, NumPut(0x1, &input + 0, "UInt")

if (status := DllCall("Gdiplus\GdiplusStartup", "Ptr*", pToken := 0, "Ptr", &input, "Ptr", 0, "Int")) {
	throw (Exception(FormatStatus(status)))
}

;* Create a Graphics object:
if (status := DllCall("Gdiplus\GdipCreateBitmapFromScan0", "UInt", 50, "UInt", 50, "UInt", 0, "UInt", 0x26200A, "Ptr", 0, "Ptr*", pBitmap := 0, "Int")) {
	throw (Exception(FormatStatus(status)))
}

if (status := DllCall("Gdiplus\GdipGetImageGraphicsContext", "Ptr", pBitmap, "Ptr*", pGraphics := 0, "Int")) {
	throw (Exception(FormatStatus(status)))
}

;* Create a Font object:
fontName := "Fira Code Retina"

if (status := DllCall("Gdiplus\GdipCreateFontFamilyFromName", "Ptr", &fontName, "Ptr", 0, "Ptr*", pFontFamily := 0, "Int")) {
	throw (Exception(FormatStatus(status)))
}

;* Create a StringFormat object:
if (status := DllCall("Gdiplus\GdipCreateStringFormat", "UInt", 0, "UInt", 0, "Ptr*", pStringFormat := 0, "Int")) {
	throw (Exception(FormatStatus(status)))
}

/*
;* enum FontStyle  ;: https://docs.microsoft.com/en-us/windows/win32/api/gdiplusenums/ne-gdiplusenums-fontstyle
	0 = FontStyleRegular
	1 = FontStyleBold
	2 = FontStyleItalic
	3 = FontStyleBoldItalic
	4 = FontStyleUnderline
	8 = FontStyleStrikeout
*/

fontStyle := 1
	, string := "String"

lineWidth := 500, lineHeight := 16

VarSetCapacity(layoutRect, 16), NumPut(0, &layoutRect + 0, "Float"), NumPut(0, &layoutRect + 4, "Float"), NumPut(lineWidth, &layoutRect + 8, "Float"), NumPut(lineHeight, &layoutRect + 12, "Float")
VarSetCapacity(boundingBox, 16)

loop {
	fontSize := A_Index

	if (status := DllCall("Gdiplus\GdipCreateFont", "Ptr", pFontFamily, "Float", fontSize, "Int", fontStyle, "UInt", 2, "Ptr*", pFont := 0, "Int")) {
		throw (Exception(FormatStatus(status)))
	}

	DllCall("Gdiplus\GdipMeasureString", "Ptr", pGraphics, "Str", string, "Int", -1, "Ptr", pFont, "Ptr", &layoutRect, "Ptr", pStringFormat, "Ptr", &boundingBox, "UInt*", chars := 0, "UInt*", lines := 0)

	DllCall("Gdiplus\GdipDeleteFont", "Ptr", pFont)
} until (NumGet(&boundingBox + 8, "Float") >= lineWidth || NumGet(&boundingBox + 12, "Float") >= lineHeight)

;* Clean up objects:
DllCall("Gdiplus\GdipDeleteStringFormat", "Ptr", pStringFormat)
DllCall("Gdiplus\GdipDeleteFontFamily", "Ptr", pFontFamily)

DllCall("Gdiplus\GdipDeleteGraphics", "Ptr", pGraphics)
DllCall("Gdiplus\GdipDisposeImage", "Ptr", pBitmap)

;* Shutdown:
DllCall("Gdiplus\GdiplusShutdown", "Ptr", pToken)
DllCall("Kernel32\FreeLibrary", "Ptr", DllCall("Kernel32\GetModuleHandle", "Str", "Gdiplus", "Ptr"), "UInt")

MsgBox, % "Limit = " . fontSize - 1 . " (" . NumGet(&boundingBox + 0, "Float") . ", " . NumGet(&boundingBox + 4, "Float") . ", " . NumGet(&boundingBox + 8, "Float") . ", " NumGet(&boundingBox + 12, "Float") . ")"

exit

;* FormatStatus(status)
FormatStatus(status) {
	Local

	Static statusLookup := {"1": "GenericError", "2": "InvalidParameter", "3": "OutOfMemory", "4": "ObjectBusy", "5": "InsufficientBuffer", "6": "NotImplemented", "7": "Win32Error", "8": "WrongState", "9": "Aborted", "10": "FileNotFound", "11": "ValueOverflow", "12": "AccessDenied", "13": "UnknownImageFormat", "14": "FontFamilyNotFound", "15": "FontStyleNotFound", "16": "NotTrueTypeFont", "17": "UnsupportedGdiplusVersion", "18": "GdiplusNotInitialized", "19": "PropertyNotFound", "20": "PropertyNotSupported", "21": "ProfileNotFound"}  ;: https://docs.microsoft.com/en-us/windows/win32/api/gdiplustypes/ne-gdiplustypes-status

	return (statusLookup[status])
}
User avatar
PuzzledGreatly
Posts: 1103
Joined: 29 Sep 2013, 22:18

Re: Font size for a string limited to two lines in length?

Post by PuzzledGreatly » 06 Jun 2021, 02:44

Thanks for the reply. I don't have the gdip library. This has been working for me:

Code: Select all

GetFontSizeForTwoLines(t,w,h, fontname := "Segoe UI") ;text, width, height
{
	static i,n,nf,nh,nw,v,wh
	
	t := instr(t,A_tab) ? Split(t,A_tab,1) : t
	;msgbox, 4096, t, % t
	
	i := 200
	loop
	{
		nf := Ceil(i * 0.9)
		Gui, dia:new, -dpiscale
		Gui, margin, 0, 0
		Gui, Font, s%i% bold, %fontname%
		Gui, add,text, vv, height
		Gui, add,text, w%w%  vn, %t%
		Guicontrolget, v, dia:pos
		Guicontrolget, n, dia:pos
		if ((nh < h) && ceil(nh/vh) < 3)
		break
		i:= nf 
	}
	Return i
}
Post Reply

Return to “Ask For Help”