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.
User avatar
Blue
Addict
Addict
Posts: 884
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 »

Funny that people in Berlin and Montreal should have the same doubts and discussions about the nature of the current season.
This planet is, indeed, a small one.

So, DPI wisdom from our resident PHC* is that we should stick to the prudent, but labour-intensive, approach. Sounds like good advice, but lots of work.
And here I am, still mourning for those glorious days when PB was completely WYSIWYG, when we didn't have to wring our brains so much just to make sure that windows and gadgets ended up looking as we dreamed them to be. Ah, the good old days of last year (or so)...

Thanks for taking the time to pass on the wisdom.


* Pointy Hat Coders
"That's not a bug..." said the programmer. "it's a feature! "
"Oh! I see..." replied the blind man.
User avatar
Blue
Addict
Addict
Posts: 884
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 »

PB 5.60 (x64) running under Windows 10 1703 on a Dell portable with a High DPI screen.

I've used Little John's code (read his post here) as an include file and tested it mercilessly. I've found it to work perfectly every single time when I compile my program. However, I kept coming across a font problem when running my code from within the IDE : often (not every time, which, in itself, is baffling... :shock: ) the font would be too large, even though it turned out to be OK in the compiled output.

Using lots of Debug statements everywhere, I finally spotted the problem: when DPI awareness is inherited from the parent, the font size gets scaled accordingly, which works fine for the compiled result, but displays wrongly when running the code from within the IDE.

So I added a test for inheritance (that's what I called it; you may disagree :( ), allowing the font size to be scaled or not, depending on whether the code is compiled or run from the IDE.

Rather than posting a heavily modified file or pointing out every single line where I've made changes, I simply reproduce below Little John's entire code, carefully marking 10 lines as either "Added" or "Modified":

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

; modified by Blue (10 lines are marked 'added' or 'modified')
; so that running from inside the IDE displays the same as the compiled output 

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
      Protected inherited=#False, dlgFontsize.f = 9.0                 ;- .   <<< Added
      
      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
            Else                               ;      <<<  Added
              inherited= #True                 ;- .   <<<  Added
            EndIf                              ;      <<<  Added
            
            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", dlgFontsize, #PB_Font_HighQuality)     ;- .   <<<  Modified
            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.
            If Not inherited                                             ;      <<<  Added
              dlgFontsize = 9.0*s_ScaleDPIy                              ;- .   <<<  Added
            EndIf                                                        ;      <<<  Added
            dlgFont = LoadFont(#PB_Any, "Segoe UI", dlgFontsize, #PB_Font_HighQuality)     ;- .   <<<  Modified
            If dlgFont
               SetGadgetFont(#PB_Default, FontID(dlgFont))
               Debug "; " + #PB_Compiler_Line +": font size set to " + dlgFontsize         ;- .   <<<  Added
            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
On my system, I've reworded the code to my liking, and made it into an include file, which has worked for me without fail ever since I started using it. I've tested th solution on a different machine with a regular 96.0 DPI screen and it also works well there. I've not been able, however, to test it for all the combinations that Little John looked into.

This is only a slight enhancement/correction to Little John's code.
I'm very grateful to him for a smart, simple and effective solution to the DPI problem.
"That's not a bug..." said the programmer. "it's a feature! "
"Oh! I see..." replied the blind man.
Mike Yurgalavage
Enthusiast
Enthusiast
Posts: 118
Joined: Thu May 17, 2007 8:35 pm
Location: USA

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

Post by Mike Yurgalavage »

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 »

Hi Blue,

I remember well that I was rather happy when I was able to come up with this code, because I desperately needed something like that. Now I am glad to read that the code is useful for you, too. And thanks for your very kind words, you are most welcome! Many thanks also for testing the code so thoroughly, and for fixing that bug. It is so good to know that someone else has tested the code rigorously! And documenting your changes so carefully is very kind.
In my message which you have referenced in your previous post, there is now a link to your improved code -- and also in the first post of this thread.

Maybe I've got a suggestion for a tiny simplification of your improvement: :-)
If I'm not mistaken, then your new variable inherited is #True if and only if the variable dpiaware is #True. So couldn't we just do without the variable inherited, and then instead of writing

Code: Select all

If Not inherited
we could write

Code: Select all

If Not dpiaware
:?:

Best regards!
User avatar
Blue
Addict
Addict
Posts: 884
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 wrote: [...]

Maybe I've got a suggestion for a tiny simplification of your improvement: :-)
If I'm not mistaken, then your new variable inherited is #True if and only if the variable dpiaware is #True. So couldn't we just do without the variable inherited, and then instead of writing

Code: Select all

If Not inherited
we could write

Code: Select all

If Not dpiaware
:?:

Best regards!
Thanks for the feedback, Little John.

Firstly, let me address your suggestion of doing away with the "inherited" Boolean.
At the risk of sounding reactionary, I would say no, it won't work. In my experience anyway. At first, I was doing exactly as you suggest; but I kept getting uneven results : sometimes the result looked fine, sometimes it didn't. That's the very "gotcha" that had me pulling my hair and going around in circles for hours. And when I write hours, it's not a figure of speech. I really spent hours figuring this out.

A piece of code flagged as dpiaware sometimes looks wrong when run from within the IDE, then looks fine, while consistently looking fine, of course, when compiled to an exe. (That part of the mystery, the inconsistency from within the IDE, I haven't figured out yet) When I finally spotted the problem line and introduced the "inherited" Boolean in all the right places, I started getting consistent results, no matter whatever the IDE is doing.

But, in the end, alas! you may be correct after all: as you know, the more you work on a bug, the less you see it. Nothing will ever beat a fresh pair of eyes coupled to a sharp brain.

Secondly, when I point out the importance of your code to users who, like me, enjoy a high DPI system, I'm not being polite or friendly. I truly mean that it has been a life saver. Your code, with its masterful little macros, has made PB enjoyable again. And I suspect that, as high DPI displays/systems become more common, more and more PB users will come to discover and appreciate it... until it gets built into PB (version 12.1 or 12.2, maybe ?)
"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,

thank you for your detailed explanation. I see now that we better should leave your code exactly as it is. And many thanks again for all your work involved in isolating and fixing the issue!
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 »

Mike Yurgalavage wrote:Had you guys read this yet?

https://blogs.windows.com/buildingapps/ ... 3vTV9Yh.97

Also looked at the api here?

https://msdn.microsoft.com/en-us/librar ... 2147217396
I have read it. However, I don't know how to transform that information into working and helpful PureBasic code. If you can do so, please share your knowledge with us.
Post Reply