Jump to content
tcurran

Pixel-accurate string width and height

Recommended Posts

tcurran

Here are two functions to provide pixel-accurate height and width dimensions for a given string.

The more commonly-used _GDIPlus_GraphicsMeasureString built-in UDF is problematic because it returns the width padded by roughly one en-space (for reasons related to the various ways Windows produces anti-aliased fonts).

These are AutoIt translations of Pierre Arnaud's C# functions, described in his CodeProject article "Bypass Graphics.MeasureString limitations"

The first is an all-purpose version that takes a window handle, string, font family, font size (in points), style, and (optionally) width of the layout column (in pixels) as parameters.

The second, more efficient version is intended for applications where GDI+ fonts are already in use, and takes handles to the existing graphics context, string, font, layout and format as parameters.

Both functions return a two-row array with the exact width [0] and height [1] of the string (in pixels).

EDIT: (Note that some of the same anti-aliasing measurement issues still apply. I did my best to work around them, but the output of the function may still be off by a pixel or two. Buyer beware.)

#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>

; #FUNCTION# ====================================================================================================================
; Name ..........: _StringInPixels
; Description ...: Returns a pixel-accurate height and width for a given string using a given font, style and size.
; Syntax ........: _StringInPixels($hGUI, $sString, $sFontFamily, $fSize, $iStyle[, $iColWidth = 0])
; Parameters ....: $hGUI                - Handle to the window.
;                  $sString             - The string to be measured.
;                  $sFontFamily         - Full name of the font to use.
;                  $fSize               - Font size in points (half-point increments).
;                  $iStyle              - Combination of 0-normal, 1-bold, 2-italic, 4-underline, 8-strikethrough
;                  $iColWidth           - [optional] If word-wrap is desired, column width in pixels
; Return values .: 2-row array. [0] is width in pixels; [1] is height in pixels.
; Author ........: Tim Curran; adapted from Pierre Arnaud's C# function
; Modified ......:
; Remarks .......: This version is longer and less efficient but works for all purposes.
; Related .......: <https://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitations>
; Link ..........:
; Example .......: Example-StringInPixels.au3
; ===============================================================================================================================
#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>

Func _StringInPixels($hGUI, $sString, $sFontFamily, $fSize, $iStyle, $iColWidth = 0)
    _GDIPlus_Startup()
    Local $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hGUI) ;Create a graphics object from a window handle

    Local $aRanges[2][2] = [[1]]
    $aRanges[1][0] = 0 ;Measure first char (0-based)
    $aRanges[1][1] = StringLen($sString) ;Region = String length

    Local $hFormat = _GDIPlus_StringFormatCreate()
    Local $hFamily = _GDIPlus_FontFamilyCreate($sFontFamily)
    Local $hFont = _GDIPlus_FontCreate($hFamily, $fSize, $iStyle)

    _GDIPlus_GraphicsSetTextRenderingHint($hGraphic, $GDIP_TEXTRENDERINGHINT_ANTIALIASGRIDFIT)
    _GDIPlus_StringFormatSetMeasurableCharacterRanges($hFormat, $aRanges) ;Set ranges

    Local $aWinClient = WinGetClientSize($hGUI)
    If $iColWidth = 0 Then $iColWidth = $aWinClient[0]
    Local $tLayout = _GDIPlus_RectFCreate(10, 10, $iColWidth, $aWinClient[1])
    Local $aRegions = _GDIPlus_GraphicsMeasureCharacterRanges($hGraphic, $sString, $hFont, $tLayout, $hFormat) ;get array of regions
    Local $aBounds = _GDIPlus_RegionGetBounds($aRegions[1], $hGraphic)
    Local $aWidthHeight[2] = [$aBounds[2], $aBounds[3]]

    ; Clean up resources
    _GDIPlus_FontDispose($hFont)
    _GDIPlus_RegionDispose($aRegions[1])
    _GDIPlus_FontFamilyDispose($hFamily)
    _GDIPlus_StringFormatDispose($hFormat)
    _GDIPlus_GraphicsDispose($hGraphic)
    _GDIPlus_Shutdown()

    Return $aWidthHeight
EndFunc   ;==>_StringInPixels


; #FUNCTION# ====================================================================================================================
; Name ..........: _StringInPixels_gdip
; Description ...: Returns a pixel-accurate height and width for a given string using a GDI+ font, layout and format
; Syntax ........: _StringInPixels_gdip($hGraphic, $sString, $hFont, $tLayout, $hFormat)
; Parameters ....: $hGraphic            - Handle to a GDI+ graphics object.
;                  $sString             - The string to be measured.
;                  $hFont               - Handle to a GDI+ font.
;                  $tLayout             - A $tagGDIPRECTF structure that bounds the string.
;                  $hFormat             - Handle to a GDI+ string format.
; Return values .: 2-row array. [0] is width in pixels; [1] is height in pixels.
; Author ........: Tim Curran; adapted from Pierre Arnaud's C# function
; Modified ......:
; Remarks .......: This much more efficient version is for use with GDI+ fonts
; Related .......:
; Link ..........: <https://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitations>
; Example .......: Example-StringInPixels.au3
; ===============================================================================================================================
#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>

Func _StringInPixels_gdip($hGraphic, $sString, $hFont, $tLayout, $hFormat)
    Local $aRanges[2][2] = [[1]]
    $aRanges[1][0] = 0 ;Measure first char (0-based)
    $aRanges[1][1] = StringLen($sString) ;Region = String length

    _GDIPlus_GraphicsSetTextRenderingHint($hGraphic, $GDIP_TEXTRENDERINGHINT_CLEARTYPEGRIDFIT)
    _GDIPlus_StringFormatSetMeasurableCharacterRanges($hFormat, $aRanges) ;Set ranges

    Local $aRegions = _GDIPlus_GraphicsMeasureCharacterRanges($hGraphic, $sString, $hFont, $tLayout, $hFormat) ;get array of regions
    Local $aBounds = _GDIPlus_RegionGetBounds($aRegions[1], $hGraphic)
    Local $aWidthHeight[2] = [$aBounds[2], $aBounds[3]]
    _GDIPlus_RegionDispose($aRegions[1])
    Return $aWidthHeight
EndFunc   ;==>_StringInPixels_gdip

 

_StringInPixels.au3

Example-StringInPixels.au3

Edited by tcurran
  • Like 4

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Similar Content

    • Sven-Seyfert
      By Sven-Seyfert
      Hi Community,

      I'm looking for a way to do a Video Overlay GUI or something like that. The idea is to create a GUI which plays a video loop (with transparency/alpha channel) in front of an other GUI. Before you asking why - because I don't believe that GDIPlus can do it out of the box. My skillset for that kind of graphical things isn't good enough to do that, but here are some specialist like @UEZ maybe who can help.

      Example alpha channel video (visualized as animated *.gif):

      I tried to do the light rays effect directly with GDIPlus, but honestly that's a bit too difficult for me. I would be very glad and grateful if there are some suggestions, ideas or recommendations.

      Code for the Video play:
       
      Example video "End.mpeg":
       
      The next challenge is that the overlay GUI should be not clickable. If I hover over the overlay area, I want to have the possibility to control the GUI or what ever, in the background. But if there is any chance to make it with GDIPlus as a Video Overlay for light rays, I would prefer that approach instead of my crazy work-around idea.

      Thanks for any suggestion - I'm grateful!
      Sven
    • Reziskonh
      By Reziskonh
      Hi, everybody
      I look for GUI whose behavior similar to the message of an email client or antivirus
      In other words:
          Any PC screen able to calculate the size
          To consider the Task bar size
          To find the lower corner on the right/below and to nestle on it

      Notes:
      In GUI the GUICtrlCreateEdit field (as option) - that can be received and displayed through variable information
      Thanks a lot
      PS
      I use the translator, excuse if something is not clear
       

    • VollachR
      By VollachR
      Hi,
      I'd like to show a progress bar for an operation performed by an external program my script is running silently, I want to show it in a GUI I created using the GUICtrlCreateProgress but I have no idea how to do it.
      The important thing to point out is that there's no way of knowing how long the external program will run, as it is a file splitter and it depends on the size of file it splits and the split parts size.
      Can someone point me in the right direction or give me an example how to do so?
      This is my RunWait command:
      RunWait($MYFILES1 & '\fsplit.exe -split ' & $Size & ' mb ' & $File & " -f " & $File & "." & $extension) It uses multiple variables declared and set earlier in the script, how will I got about having the progress of that command shown using GUICtrlCreateProgress ? Is it possible?
      Thank you.
    • nacerbaaziz
      By nacerbaaziz
      Hello my friends
      I have an inquiry and I hope to find the answer here
      I want to create a graphical user interface
      but I want to hide the system menu
      I mean the window menu
      Is this possible?
      If is possible please give me how to do that
      Thanks in advance
    • c.haslam
      By c.haslam
      I had thought that _GDIPlus_ImageClone($hImage) removes all property items, but I now know that it copies property items.
      What is the easiest way of copying an image that has property items to another image that does not have property items?
×