Win 8.1+ AND PB 5.40+: Fonts too small in DPI-aware progs

Just starting out? Need help? Post your questions and find answers here.
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Win 8.1+ AND PB 5.40+: Fonts too small in DPI-aware progs

Post by Little John »

Hi,

when making a program DPI-aware on Windows 10, PB 5.31 and PB 5.40 beta 8 behave differently as for font size.

//edit 2017-06-02:
See this post below with code that seems to solve the problem.

//edit 2017-08-11:
Even better, see Blue's improved version on page 2. Many thanks for that!


The following test code is based on code by Rescator, but uses the new (i.e. Windows 8.1+) functions GetProcessDpiAwareness() and SetProcessDpiAwareness().

Code: Select all

; -- Windows 8.1+ (tested on Windows 10)

EnableExplicit

; Constants for Get/SetProcessDpiAwareness()
Enumeration
   #Process_DPI_Unaware
   #Process_System_DPI_Aware
   #Process_Per_Monitor_DPI_Aware
EndEnumeration

Prototype.i pGetProcessDpiAwareness (hprocess.i, *level)
Prototype.i pSetProcessDpiAwareness (level.i)

Global.f g_ScaleDPIx=1.0, g_ScaleDPIy=1.0


Procedure InitScaleDPI()
   Protected GetProcessDpiAwareness.pGetProcessDpiAwareness
   Protected SetProcessDpiAwareness.pSetProcessDpiAwareness
   Protected.i shcore, hdc, dpiaware=#Process_DPI_Unaware
   
   ; Only use this in EXEs, as DLLs inherit DPI from the calling process.
   CompilerIf #PB_Compiler_ExecutableFormat = #PB_Compiler_Executable
      shcore = OpenLibrary(#PB_Any, "shcore.dll")
      If shcore
         GetProcessDpiAwareness = GetFunction(shcore, "GetProcessDpiAwareness")
         If GetProcessDpiAwareness
            If GetProcessDpiAwareness(#Null, @ dpiaware) = #S_OK
               Debug "Get: OK"
            EndIf  
            Debug "DPI-awareness: " + dpiaware
         EndIf
         
         ; If the exe is allready dpi aware (like through a manifest), then we skip using
         ; the SetProcessDpiAwareness function.
         If dpiaware = #Process_DPI_Unaware
            SetProcessDpiAwareness = GetFunction(shcore, "SetProcessDpiAwareness")
            If SetProcessDpiAwareness
               If SetProcessDpiAwareness(#Process_System_DPI_Aware) = #S_OK
                  Debug "Set: OK"
               EndIf
            EndIf
         EndIf
         
         CloseLibrary(shcore)
      EndIf
   CompilerEndIf
   
   hdc = GetDC_(#Null)     ; get handle to the device context for the entire screen
   If hdc
      g_ScaleDPIx = GetDeviceCaps_(hdc, #LOGPIXELSX) / 96.0    ; 96 is the default DPI value on Windows
      g_ScaleDPIy = GetDeviceCaps_(hdc, #LOGPIXELSY) / 96.0
      ReleaseDC_(#Null, hdc)
   EndIf
EndProcedure


Macro DPIx (_x_)
   (_x_) * g_ScaleDPIx
EndMacro
Macro DPIy (_y_)
   (_y_) * g_ScaleDPIy
EndMacro

Define font

InitScaleDPI()

font = LoadFont(#PB_Any, "Segoe UI", 9, #PB_Font_HighQuality)
If font = 0
   MessageRequester("Error", "Unable to load font.")
   End
EndIf
SetGadgetFont(#PB_Default, FontID(font))

If OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, DPIx(180), DPIy(110), "PB " + StrF(#PB_Compiler_Version/100, 2)) = 0
   MessageRequester("Error", "Unable to open main window.")
   End
EndIf   
TextGadget(#PB_Any, DPIx(20), DPIy(20), DPIx(80), DPIy(30), "ScaleDPIy: " + StrF(g_ScaleDPIy, 2))
TextGadget(#PB_Any, DPIx(20), DPIy(60), DPIx(80), DPIy(30), "Segoe UI 9 pt")

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Here are screenshots of the created windows with PB 5.31 and PB 5.40 beta 8 on DPI settings 125% and 200%. The screenshots also contain the corresponding control panel dialog, which shows example text in the proper size for comparison.

Image

Image

Image

Image

Why do PB 5.31 and PB 5.40 beta 8 produce different results here?
Is there something wrong with my code, or is this a bug in PB 5.40 beta 8?

PS: Without calling any DPI related Windows API functions in the code, everything works as expected.
Last edited by Little John on Fri Aug 11, 2017 7:21 pm, edited 2 times in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by GPI »

I think pb must change something with the LoadFont()-function

Sorry, i can't test it here, but when you outcommend this section:

Code: Select all

    ;font = LoadFont(#PB_Any, "Segoe UI", 9, #PB_Font_HighQuality)
    ;If font = 0
    ;   MessageRequester("Error", "Unable to load font.")
    ;   End
    ;EndIf
    ;SetGadgetFont(#PB_Default, FontID(font))
Does it work then?

Maybe you can try to load a font with the windows-api direct and use then the handle with SetGadgetFont().
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Little John »

Hi GPI,

thanks for your reply.
GPI wrote:Sorry, i can't test it here, but when you outcommend this section:

Code: Select all

    ;font = LoadFont(#PB_Any, "Segoe UI", 9, #PB_Font_HighQuality)
    ;If font = 0
    ;   MessageRequester("Error", "Unable to load font.")
    ;   End
    ;EndIf
    ;SetGadgetFont(#PB_Default, FontID(font))
Does it work then?
Then the results are the same with PB 5.31 and PB 5.40 beta 8: Fonts are always too small for higher DPI values. :mrgreen:

Image

Image

Image

Image

GPI wrote:Maybe you can try to load a font with the windows-api direct and use then the handle with SetGadgetFont().
That's a good idea. Unfortunately, I don't know how to do so.
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Thunder93 »

Using PureBasic 5.60 Stable under Windows 10.

The code provided, don't set the correct font size. The correct font size would be what is specified with my Windows. What is being set by the code is much too small. Easily addressed, and something else that's not just specific to your code.

Using this latest API or the previous API for setting DPI awareness. I can't seem to get PureBasic to apply the ClearType to the font. Is this a bug with PB itself and #PB_Font_HighQuality usage when making your apps DPI Aware? .. in any case It looks weird, and I've tried to apply ClearType .. as it's used by default on my Windows. However to no avail!
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Thunder93 »

Just remembered something, I wasn't switching to TrueType version of Windows default font, when making the application DPI-Aware. My bad! Now she's okay :)
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
Rinzwind
Enthusiast
Enthusiast
Posts: 638
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Rinzwind »

So what is the fix to make the code in the first post work in both Windows 7 and 10? On 10 it is way too small... Also, when will DPI awareness 'just work'? Its annoying to have to add helper function everywhere and it does not work with the designer generated code...
User avatar
chi
Addict
Addict
Posts: 1034
Joined: Sat May 05, 2007 5:31 pm
Location: Linz, Austria

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by chi »

Rinzwind wrote:So what is the fix to make the code in the first post work in both Windows 7 and 10?

Code: Select all

If g_ScaleDPIy <= 1.25
  font = LoadFont(#PB_Any, "Segoe UI", 9, #PB_Font_HighQuality)
Else
  font = LoadFont(#PB_Any, "Segoe UI", 9 * g_ScaleDPIy, #PB_Font_HighQuality)
EndIf
Et cetera is my worst enemy
Rinzwind
Enthusiast
Enthusiast
Posts: 638
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Rinzwind »

The thing is that loadfont works differently on Windows 7 compared to 10 (see also the opening post). There the font is scaled correctly without any modification. In windows 10 its not. So wheres that comming from? Youre code fixes it in Windows 10, but makes the font too large in Windows 7. Wonder why "automatic" dpi support is not added yet. In an old interview Fred answers "soon" to the question when its available. Do we really need another hack that checks the os version for this too? And what about Windows 8?
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Little John »

Oops, I had almost forgotten about this thread. :-)

Solution

This is what I'm doing on Windows for quite some time now. As far as I can see, it works fine.

//edit 2017-06-24
  • Inside the module:
    Now even when compiled with PB versions < 5.40, the code is able to reliably detect whether it is running on Windows 8.1 or newer.
  • Demo code:
    Simplified

Code: Select all

; successfully tested with all combinations of
; - Windows XP, 7, 10 Creators update
; - DPI 125%, 150%
; - PB 5.31, 5.44 LTS, 5.60


DeclareModule Std
   ; [...]
   
   Declare.f FactorDPIx()
   Declare.f FactorDPIy()
EndDeclareModule


Module Std
   EnableExplicit
   
   ; [...]
   
   CompilerIf #PB_Compiler_Version < 540
      Structure RTL_OSVERSIONINFOEXW
         dwOSVersionInfoSize.l
         dwMajorVersion.l
         dwMinorVersion.l
         dwBuildNumber.l
         dwPlatformId.l
         szCSDVersion.u[128]
         wServicePackMajor.w
         wServicePackMinor.w
         wSuiteMask.w
         wProductType.b
         wReserved.b
      EndStructure
      
      Prototype.i pRtlGetVersion (*ver.RTL_OSVERSIONINFOEXW)
      
      #STATUS_SUCCESS = 0
      
      Procedure.i _IsWindows81OrNewer()
         ; In PB 5.31, OSVersion() returns e.g. 90 on Windows 8 and also
         ; on Windows 10. So with older PB versions that function can't
         ; be used to check the current Windows version reliably.
         ; Therefore in order to check whether the current OS is Windows
         ; 8.1 or newer, this self-written procedure is used here.
         ;
         ; out: #True if the currently running OS is Windows 8.1 or newer,
         ;      #False otherwise
         Protected ver.RTL_OSVERSIONINFOEXW
         Protected RtlGetVersion.pRtlGetVersion
         Protected hDLL.i
         
         ver\dwOSVersionInfoSize = SizeOf(ver)
         
         hDLL = OpenLibrary(#PB_Any, "ntdll.dll")
         If hDLL
            RtlGetVersion = GetFunction(hDLL, "RtlGetVersion")
            CloseLibrary(hDLL)
         EndIf
         
         If RtlGetVersion = 0 Or RtlGetVersion(@ ver) <> #STATUS_SUCCESS
            ProcedureReturn #False
         EndIf
         
         If ver\dwPlatformId <> #VER_PLATFORM_WIN32_NT
            ProcedureReturn #False
         EndIf
         
         If (ver\dwMajorVersion = 6 And ver\dwMinorVersion = 3) Or
            ver\dwMajorVersion > 6
            ProcedureReturn #True
         EndIf  
         
         ProcedureReturn #False
      EndProcedure
   CompilerEndIf   
   
   
   Prototype.i pIsProcessDPIAware()
   Prototype.i pSetProcessDPIAware()
   
   Define s_InitDPI.i=#False, s_ScaleDPIx.f=1.0, s_ScaleDPIy.f=1.0
   
   Procedure _InitScaleDPI()
      ; Windows 5.0 or higher needed for minimum functionality of this procedure
      ; [modified after <http://www.purebasic.fr/english/viewtopic.php?f=12&t=40507>, 2010-01-02
      ;  see also <http://msdn.microsoft.com/en-us/library/windows/desktop/dd464660%28v=vs.85%29.aspx>
      ;           <http://blogs.msdn.com/b/oldnewthing/archive/2004/07/14/182971.aspx>
      ;           <http://www.purebasic.fr/english/viewtopic.php?p=462177#p462177>
      ;           <http://www.purebasic.fr/english/viewtopic.php?f=13&t=62043>]
      Shared s_InitDPI, s_ScaleDPIx, s_ScaleDPIy
      Protected IsProcessDPIAware.pIsProcessDPIAware
      Protected SetProcessDPIAware.pSetProcessDPIAware
      Protected.i user32, hdc, dlgFont, dpiaware=#False
      
      CompilerIf #PB_Compiler_ExecutableFormat = #PB_Compiler_Executable
         ; Only use this in EXEs, as DLLs inherit DPI from the calling process.
         ; This part is Windows 6.x+ only (Vista and newer) and must be done before using
         ; GetDeviceCaps().
         user32 = OpenLibrary(#PB_Any, "user32.dll")
         If user32
            IsProcessDPIAware = GetFunction(user32, "IsProcessDPIAware")
            If IsProcessDPIAware
               dpiaware = IsProcessDPIAware()
            EndIf
            CompilerIf #PB_Compiler_IsMainFile
               Debug "DPI-aware: " + dpiaware
            CompilerEndIf
            
            ; If the exe is allready DPI aware (like through a manifest), then we skip using
            ; the set DPI aware function.
            If dpiaware = #False
               SetProcessDPIAware = GetFunction(user32, "SetProcessDPIAware")
               If SetProcessDPIAware
                  If SetProcessDPIAware()
                     CompilerIf #PB_Compiler_IsMainFile
                        Debug "Set DPI: OK"
                     CompilerEndIf
                  EndIf
               EndIf
            EndIf
            
            CloseLibrary(user32)
         EndIf
      CompilerEndIf
      
      hdc = GetDC_(#Null)     ; get handle to the device context for the entire screen
      If hdc
         s_ScaleDPIx = GetDeviceCaps_(hdc, #LOGPIXELSX) / 96.0    ; 96 is the default DPI value on Windows.
         s_ScaleDPIy = GetDeviceCaps_(hdc, #LOGPIXELSY) / 96.0
         ReleaseDC_(#Null, hdc)
      EndIf
      
      CompilerIf #PB_Compiler_Version < 540
         If _IsWindows81OrNewer()
            CompilerIf #PB_Compiler_IsMainFile
               Debug "Windows 8.1 or newer"
            CompilerEndIf  
            ; Here the font sizes are adjusted automatically.
            dlgFont = LoadFont(#PB_Any, "Segoe UI", 9.0, #PB_Font_HighQuality)
            If dlgFont
               SetGadgetFont(#PB_Default, FontID(dlgFont))
            EndIf
         EndIf 
      CompilerElse   
         If OSVersion() >= #PB_OS_Windows_8_1
            CompilerIf #PB_Compiler_IsMainFile
               Debug "Windows 8.1 or newer"
            CompilerEndIf  
            ; Here the font sizes are not adjusted automatically.
            dlgFont = LoadFont(#PB_Any, "Segoe UI", 9.0*s_ScaleDPIy, #PB_Font_HighQuality)
            If dlgFont
               SetGadgetFont(#PB_Default, FontID(dlgFont))
            EndIf
         EndIf   
      CompilerEndIf
      
      s_InitDPI = #True
   EndProcedure
   
   Procedure.f FactorDPIx()
      Shared s_InitDPI, s_ScaleDPIx
      
      If s_InitDPI = #False
         _InitScaleDPI()
      EndIf
      
      ProcedureReturn s_ScaleDPIx
   EndProcedure
   
   Procedure.f FactorDPIy()
      Shared s_InitDPI, s_ScaleDPIy
      
      If s_InitDPI = #False
         _InitScaleDPI()
      EndIf
      
      ProcedureReturn s_ScaleDPIy
   EndProcedure
EndModule


CompilerIf #PB_Compiler_IsMainFile
   ; -- Demo
   EnableExplicit
   
   Global g_ScaleDPIx.f, g_ScaleDPIy.f
   
   g_ScaleDPIx = Std::FactorDPIx()
   g_ScaleDPIy = Std::FactorDPIy()
   
   Macro DPIx (_x_)
      (_x_) * g_ScaleDPIx
   EndMacro
   Macro DPIy (_y_)
      (_y_) * g_ScaleDPIy
   EndMacro
   
   
   If OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, DPIx(120), DPIy(70), "" ) = 0
      MessageRequester("Fatal error", "Can't open main window.")
      End
   EndIf   
   TextGadget(#PB_Any, DPIx(30), DPIy(20), DPIx(60), DPIy(20), "DPI: " + StrF(100*g_ScaleDPIy,0) + " %", #PB_Text_Border)
   
   Repeat
   Until WaitWindowEvent() = #PB_Event_CloseWindow
CompilerEndIf
Last edited by Little John on Sat Jun 24, 2017 5:29 pm, edited 1 time in total.
User avatar
chi
Addict
Addict
Posts: 1034
Joined: Sat May 05, 2007 5:31 pm
Location: Linz, Austria

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by chi »

It still doesn't work on Windows 7 using 150% (no XP dpi). Must have something to do with "Use Windows XP style DPI scaling"...

Btw: MS reworked the DPI system in Windows 8.1 (read here), that's why it's working on Windows 10.
Et cetera is my worst enemy
User avatar
chi
Addict
Addict
Posts: 1034
Joined: Sat May 05, 2007 5:31 pm
Location: Linz, Austria

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by chi »

Ok, I think I found the problem... There seems to be a difference between activating DPI-awareness via code or manifest! Little John's last code only works on Win7 (150%, No XP DPI) if the awareness was set via manifest:

Code: Select all

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
  <assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="X86"
    name="CompanyName.ProductName.YourApp"
    type="win32" />
  <description></description>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        processorArchitecture="X86"
        publicKeyToken="6595b64144ccf1df"
        language="*" />
    </dependentAssembly>
  </dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
       </security>
  </trustInfo>

  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>

</assembly>
Maybe it's time to add a new "Application is DPI aware" checkbox to the compiler options ;)
Et cetera is my worst enemy
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: [5.40 beta 8] Fonts too small in DPI-aware programs

Post by Little John »

chi wrote:Btw: MS reworked the DPI system in Windows 8.1
That's why the code in my previous post contains the line

Code: Select all

If OSVersion() > #PB_OS_Windows_8
chi wrote:Little John's last code only works on Win7 (150%, No XP DPI) if the awareness was set via manifest
That is contrary to my experience.

The code in my previous post worked fine here on all combinations of Windows XP, 7, and 10 with PureBasic 5.31 and 5.44. And I never have set DPI awareness via manifest. I just used that code.
User avatar
chi
Addict
Addict
Posts: 1034
Joined: Sat May 05, 2007 5:31 pm
Location: Linz, Austria

Re: Win 8.1+ AND PB 5.40+: Fonts too small in DPI-aware prog

Post by chi »

Maybe because I'm on PB 5.60?! Did you check your code on 5.60?

edit: I just checked multiple versions of PB (x86 on Windows 7 x64):

5.31: OK
5.41 LTS: NO
5.42 LTS: NO
5.50: NO
5.60: NO
Et cetera is my worst enemy
User avatar
Blue
Addict
Addict
Posts: 886
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Re: Win 8.1+ AND PB 5.40+: Fonts too small in DPI-aware prog

Post by Blue »

@ Little John

Greetings Little John !
Hope summer agrees with you.
It sure does with my old bones.

Down to business :
I very much like your short and sweet macro approach to the scaling of coordinates and sizes.
It makes for clear, concise and smart looking code. (I quite admire your Mastery of macros).

I'm just wondering why you insist on maintaining separate scalings for X and Y values.
From everything I've read on the subject, DPI values, whether horizontal or vertical, are always identical.
So why not simplify everything and use only a single value — and corresponding macro — for all axis ?
I figure there must be a reason behind the extra work, but i'm not quite able to see it yet.
Any explanation ?
"That's not a bug..." said the programmer. "it's a feature! "
"Oh! I see..." replied the blind man.
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Win 8.1+ AND PB 5.40+: Fonts too small in DPI-aware prog

Post by Little John »

Hello Blue,

I'm pleased to "see" you! And thanks for your kind words.
Yesterday, we had a little discussion about whether or not what is currently happening here in Berlin should actually be called "summer". :-) However, I am happy with it.
Blue wrote:I'm just wondering why you insist on maintaining separate scalings for X and Y values.
From everything I've read on the subject, DPI values, whether horizontal or vertical, are always identical.
So why not simplify everything and use only a single value — and corresponding macro — for all axis ?
I was wondering why, too. :-)
Then some very knowledgeable people such as luis and Danilo told me, that it's not safe to assume that DPI factors are always the same on x and y axis.
Post Reply