Cross platform font ascent/descents!

Share your advanced PureBasic knowledge/code with the community.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Cross platform font ascent/descents!

Post by srod »

**EDIT : code updated with assistance from Rashad.

==================================

Original post :

I just found myself needing to draw a single line of text containing multiple fonts.

Problem is that PB's 2D drawing lib does not allow us to draw along a common baseline because we cannot calculate the necessary font metrics without dipping into Win API and GetTextMetrics_(). Using DrawText() repeatedly to create a single line of text containing multiple fonts produces a lump of text sharing the same top line which is useless and very very ugly.

Since I need this to be cross-platform, I hacked up the following which makes simple use of the vector lib to calculate the necessary metrics which we can then use in the regular 2D drawing lib. Only tested on Windows and Ubuntu, but I see no reason why it should not work on the other platforms.

Note that the vector lib returns slightly different values for text ascents than the Win API GetTextMetrics_(), but these differences are small and possibly due to rounding errors because the vector lib uses floating point values etc. Doesn't seem to impact my tests thus far.

Thought this might be useful.

Code: Select all

;/////////////////////////////////////////////////////////////////////////////////
;Font ascent / Descent.
;======================
;srod + Rashad : Jan 2019.
;
;With PB's vector library it is possible to draw text in multiple fonts on a single line sharing a common baseline.
;This is not currently possible with PB's 2D Drawing lib (not in a cross-platform way) since we do not have access to the necessary text metrics (ascent, descent, internal leading etc.)
;This little prog shows how to use the cross-platform vector lib to 'estimate' the ascents/descents of a given font for subsequent use 
;with PB's basic 2D Drawing lib.
;With this info it is then possible to use the 2D drawing lib to draw text in multiple fonts on a single line sharing a common baseline as we demonstrate.
;
;Platforms : ALL.  Tested on Windows/Ubuntu (GTK3 and QT) only.
;
;NOTES.
;
;     i)  During testing we found an incompatibility between PB's vector library and PB's 2D Drawing lib with certain 'wayward' fonts. 
;         This necessitated a Rashad inspired workaround which results in 'estimates' for the required font ascents etc. 
;         These estimates are looking very good though. Certainly good enough for the purposes of this utility.
;/////////////////////////////////////////////////////////////////////////////////


;The following global records the max ascent of the various fonts we are using. 
;This will allow us to draw a single line of text along a common baseline with multiple fonts etc.
  Global gMaxAscent.d

;The following structure will hold ascent/descent info on each of our fonts.
  Structure fontData
    font.i
    ascent.d
  EndStructure

;We place all our fonts in an array for convenience.
  Dim fonts.fontData(3)
  fonts(1)\font = LoadFont(#PB_Any, "Monotype Corsiva", 45)
  fonts(2)\font = LoadFont(#PB_Any, "Times New Roman", 30, #PB_Font_Italic)
  fonts(3)\font = LoadFont(#PB_Any, "Arial", 11)

Declare GetFontData(Array fonts.fontData(1))

;Calculate font ascents/descents for all of our fonts.
  Global text$ = "ABCDefghIJK"
  GetFontData(fonts())

;A quick test to check all is well.
;We simply use DrawText() 3 times (once for each font) to display a single line of repeated text adjusting the vertical position to ensure
;all the text shares the same baseline.
Define.d tempAscent
If OpenWindow(0, 0, 0, 800, 400, "Font Data", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(0, 10, 10, 780, 380)
  If StartDrawing(CanvasOutput(0))
    ;Draw a single line of text in the various fonts sharing the same baseline.
      DrawingMode(#PB_2DDrawing_Transparent)
      For i = 1 To ArraySize(fonts())      
        DrawingFont(FontID(fonts(i)\font))
        x=DrawText(x, 20 + gMaxAscent - fonts(i)\ascent, text$, 0)
      Next
    ;Draw the baseline.
      Line(0, 20 + gMaxAscent, x, 1, $0000FF)
    StopDrawing()
  EndIf
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf

End

;The following procedure calculates all font ascents/descents.
;It uses the vector lib for cross platform purposes.
Procedure GetFontData(Array fonts.fontData(1))
  Protected image, i, TextHeight, visHeight.d, ascentScale.d
  ;Create a dummy image.
    image = CreateImage(#PB_Any, 1, 1)
  If image
    If StartVectorDrawing(ImageVectorOutput(image))
      If StartDrawing(ImageOutput(image))
        For i = 1 To ArraySize(fonts())  
          VectorFont(FontID(fonts(i)\font))
          visHeight = VectorTextHeight(text$, #PB_VectorText_Visible) + VectorTextHeight(text$, #PB_VectorText_Visible|#PB_VectorText_Offset)
          If visHeight
            ascentScale = VectorTextHeight(" ", #PB_VectorText_Baseline) / visHeight
          Else
            ascentScale = 1
          EndIf
          DrawingFont(FontID(fonts(i)\font))
          fonts(i)\ascent = ascentScale * TextHeight(" ")
          If fonts(i)\ascent > gMaxAscent
            gMaxAscent = fonts(i)\ascent
          EndIf
        Next
        StopDrawing()
      EndIf
      StopVectorDrawing()
    EndIf
    FreeImage(image)  
  EndIf
EndProcedure
Last edited by srod on Thu Jan 17, 2019 10:36 am, edited 6 times in total.
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

mohsen wrote:excellent.
I really needed these codes.I think with this function i can simulate the markup-text feature.
Thanks a lot.
You're welcome Mohsen. Just remember to recalculate the ascent/descents every time you alter a font etc.
I may look like a mule, but I'm not a complete ass.
User avatar
Kwai chang caine
Addict
Addict
Posts: 4915
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Cross platform font ascent/descents!

Post by Kwai chang caine »

Hello SROD always happy to read you :D
Works perfectly here with W7 v5.70
Thanks you for sharing 8)
ImageThe happiness is a road...
Not a destination
davido
Addict
Addict
Posts: 1841
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Cross platform font ascent/descents!

Post by davido »

@srod,

Checked on Windows 10.
Checked it on my MacBook Pro, too.
Looks pretty much the same to me.

Thank you for sharing. :D
DE AA EB
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 3996
Joined: Sun Apr 12, 2009 6:27 am

Re: Cross platform font ascent/descents!

Post by RASHAD »

Hi srod
Simple using only the VectorLib

Code: Select all

  LoadFont(0, "Arial", 42)
  LoadFont(1, "Times New Roman", 20, #PB_Font_Italic)
  LoadFont(2, "Arial", 30)
  
  If OpenWindow(0, 0, 0, 610, 200, "VectorDrawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    CanvasGadget(0, 0, 0, 610, 200)
    
    If StartVectorDrawing(CanvasVectorOutput(0))
      text$ = "ABCDefghIJK"
      VectorFont(FontID(0), 40)
      x1 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      h1 = VectorTextHeight(Text$,#PB_VectorText_Visible)
      VectorSourceColor(RGBA(0, 0, 0, 250))
      MovePathCursor(10 ,10)
      DrawVectorText(Text$)
            
      VectorFont(FontID(1), 20)
      VectorSourceColor(RGBA(0, 250, 0, 250))
      x2 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      h2 = VectorTextHeight(Text$,#PB_VectorText_Visible) + 1
      MovePathCursor(x1+12 ,11 + h1-h2)
      DrawVectorText(Text$)
      
      VectorFont(FontID(2), 35)
      VectorSourceColor(RGBA(0, 0, 250, 250))
      x3 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      h3 = VectorTextHeight(Text$,#PB_VectorText_Visible) + 1
      MovePathCursor(x1+x2+14,12 + h1-h3)
      DrawVectorText(Text$)
      
      ResetCoordinates()
      VectorSourceColor(RGBA(250, 0, 0, 250))
      MovePathCursor(10 ,8 + h1)     
      AddPathLine(x1+x2+x3+14,10 + h1)
      StrokePath(1)      
      StopVectorDrawing()
    EndIf
    
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
  EndIf
Edit : Modified
Last edited by RASHAD on Wed Jan 16, 2019 7:06 pm, edited 1 time in total.
Egypt my love
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

I needed the calculations for use with the 2D Drawing lib, not the vector library.

I use the 2D Drawing lib instead of the vector lib when speed is critical.
I may look like a mule, but I'm not a complete ass.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 3996
Joined: Sun Apr 12, 2009 6:27 am

Re: Cross platform font ascent/descents!

Post by RASHAD »

Got it
Thanks
Any way I archived it (I knew that srod can not do it for nothing :) )
Egypt my love
User avatar
skywalk
Addict
Addict
Posts: 3516
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Cross platform font ascent/descents!

Post by skywalk »

Yes, RASHAD, I use the vector lib for anti-aliased text.
Please delete this line from your example to avoid confusion:
LoadFont(0, "Tahoma", 20)
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

I must admit Rashad that I don't see how yours should work - doesn't make sense to me. In fact it fails with the Monotype Corsiva font.

Then again my code fails as well with this font! :) Seems that the baseline height returned from the VectorTextHeight() function differs too much from that returned for the true text ascent with GetTextMetrics_().

Will need to examine this a bit more.
I may look like a mule, but I'm not a complete ass.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 3996
Joined: Sun Apr 12, 2009 6:27 am

Re: Cross platform font ascent/descents!

Post by RASHAD »

@skywalk
Hi
Previous post updated
Fixed also Line width using Old workaround

@srod
I never found any standard way to get the perfect text width & height
Specially with different font sizes
Glad to see you back active
Egypt my love
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

Found the problem with my code. There seems to be a distinct incompatibility/discrepancy between the regular DrawText() and DrawVectorText() which you can see in the following. The left canvas is drawn with the vector lib and the right canvas with the regular 2D lib.

Code: Select all

If OpenWindow(0, 0, 0, 800, 250, "VectorDrawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(0, 0, 0, 350, 250)
  CanvasGadget(1, 400, 0, 350, 250)
  LoadFont(0, "Monotype Corsiva", 45, #PB_Font_Italic)
  Text$ = "ABCDefghIJK"
  If StartVectorDrawing(CanvasVectorOutput(0))
    VectorFont(FontID(0))
    MovePathCursor(0, 0)
    DrawVectorText(Text$)
    StopVectorDrawing()
  EndIf
  If StartDrawing(CanvasOutput(1))
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawingFont(FontID(0))
    DrawText(0, 0, Text$, 0)        
    StopDrawing()
  EndIf
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf
I would have expected both text drawing's to have been placed identically. If this is an issue then it seems to be with the vector lib as the ascent reported by GetTextMetrics_() is totally correct with the right canvas and DrawText().

Puzzling.
I may look like a mule, but I'm not a complete ass.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 3996
Joined: Sun Apr 12, 2009 6:27 am

Re: Cross platform font ascent/descents!

Post by RASHAD »

Using Font Base Line as origin

Code: Select all

  LoadFont(0, "Arial", 42)
  LoadFont(1, "Times New Roman", 20, #PB_Font_Italic)
  LoadFont(2, "Monotype Corsiva", 30)
  
  If OpenWindow(0, 0, 0, 610, 200, "VectorDrawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    CanvasGadget(0, 0, 0, 610, 200)
    
    If StartVectorDrawing(CanvasVectorOutput(0))
      text$ = "ABCDefghIJK"
      VectorFont(FontID(0), 40)
      x1 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      hb1 = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      VectorSourceColor(RGBA(0, 0, 0, 250))
      MovePathCursor(10 ,10)
      DrawVectorText(Text$)
            
      VectorFont(FontID(1), 20)
      VectorSourceColor(RGBA(0, 250, 0, 250))
      x2 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      hb2 = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      MovePathCursor(x1+12 ,10 + hb1 - hb2)
      DrawVectorText(Text$)
      
      VectorFont(FontID(2), 35)
      VectorSourceColor(RGBA(0, 0, 250, 250))
      x3 = VectorTextWidth(Text$,#PB_VectorText_Visible)
      hb3 = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      MovePathCursor(x1+x2+14,10 + hb1 - hb3)
      DrawVectorText(Text$)
      
      ResetCoordinates()
      VectorSourceColor(RGBA(250, 0, 0, 250))
      MovePathCursor(10 ,8 + hb1)     
      AddPathLine(x1+x2+x3+14,10 + hb1)
      StrokePath(1)      
      StopVectorDrawing()
    EndIf
    
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
  EndIf
Egypt my love
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

Yes there is no problem with these fonts when using the vector lib and the baseline height - providing you don't then switch across to the 2D lib! :) That was why I didn't understand your original code because you were not using the baseline height.

Have tested with more fonts and similar problems arise with the 'italicised' type fonts which seem to be rendered by GDI with a huge internal leading space which is not happening with the vector drawing.

Everything works fine with the more common fonts and I can get these 'bad' fonts to work fine if I use API to determine the font ascents and baselines etc. Even tested with GDI directly and exactly the same as using PB's 2D drawing. Yes, would seem that the vector lib is stripping out this additional leading space somehow and for some reason.

Was hoping for a fool proof cross-platform solution which, sadly, is not happening right now.
I may look like a mule, but I'm not a complete ass.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 3996
Joined: Sun Apr 12, 2009 6:27 am

Re: Cross platform font ascent/descents!

Post by RASHAD »

Workaround

Code: Select all

   LoadFont(0, "Arial", 20)
  LoadFont(1, "Times New Roman", 14, #PB_Font_Italic)
  LoadFont(2, "Monotype Corsiva", 24)
 
  If OpenWindow(0, 0, 0, 500, 200, "VectorDrawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    CanvasGadget(0, 0, 0, 500, 200)
    text$ = "ABCDefghIJK"
    
    If StartVectorDrawing(CanvasVectorOutput(0))      
      VectorFont(FontID(0), 120)
      h1.f = VectorTextHeight(Text$,#PB_VectorText_Visible)
      hb1.f = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      scale1.f = hb1/h1
                
      VectorFont(FontID(1), 120)
      h2.f = VectorTextHeight(Text$,#PB_VectorText_Visible)
      hb2.f = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      scale2.f = hb2/h2
     
      VectorFont(FontID(2), 120)
      h3.f = VectorTextHeight(Text$,#PB_VectorText_Visible)
      hb3.f = VectorTextHeight(Text$,#PB_VectorText_Baseline)
      scale3.f = hb3/h3
      StopVectorDrawing()
    EndIf
    
    If StartDrawing(CanvasOutput(0))
      DrawingMode(#PB_2DDrawing_Transparent)
      DrawingFont(FontID(0))
      x1.f = TextWidth(Text$)
      h1.f = TextHeight(Text$)
      DrawText(10 ,10 ,Text$,$0)
           
      DrawingFont(FontID(1))
      x2.f = TextWidth(Text$)
      h2.f = TextHeight(Text$)
      DrawText(12+x1,10 + h1*scale1 - h2*scale2 , Text$ , $00FF00)
     
      DrawingFont(FontID(2))
      x3.f = TextWidth(Text$)
      h3.f = TextHeight(Text$)
      DrawText(14+x1+x2,10 + h1*scale1- h3*scale3 , Text$ ,$0000FF)
      
      Line(10,10+h1*scale3,x1+x2+x3,1,$FF0000)      
      StopDrawing()
    EndIf
   
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
  EndIf
Egypt my love
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Cross platform font ascent/descents!

Post by srod »

Rashad, that is a serious case of thinking outside of the box! In fact, you're so far out of the box I'm surprised you haven't fallen out and broken your neck! :)

You flipping genius!

I made a couple of tweaks (basically added the visible offset) and tests thus far seem very good. Only trouble with this is that the #PB_VectorText_Visible flag ideally requires that the actual text to be printed in each font is supplied up front whilst calculating the metrics (since the resulting text height depends on the text given) which is no good for me and should not be required for the calculation of the font metrics anyhow. A font ascent does not depend on any given set of characters to be printed.

However, for my purposes, if I switch to #PB_VectorText_Default I do not need to supply any text and the resulting loss of accuracy seems negligible. Certainly good enough for my purposes.

Nice one mate. I don't think I would have thought of trying that, though it is kind of obvious now! :)

Will update the code in the first post.
I may look like a mule, but I'm not a complete ass.
Post Reply