PureBasic Forum
http://forums.purebasic.com/english/

Cross platform font ascent/descents!
http://forums.purebasic.com/english/viewtopic.php?f=12&t=72099
Page 1 of 1

Author:  srod [ Wed Jan 16, 2019 1:18 pm ]
Post subject:  Cross platform font ascent/descents!

**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:
;/////////////////////////////////////////////////////////////////////////////////
;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

Author:  srod [ Wed Jan 16, 2019 1:49 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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.

Author:  Kwai chang caine [ Wed Jan 16, 2019 2:21 pm ]
Post subject:  Re: Cross platform font ascent/descents!

Hello SROD always happy to read you :D
Works perfectly here with W7 v5.70
Thanks you for sharing 8)

Author:  davido [ Wed Jan 16, 2019 2:24 pm ]
Post subject:  Re: Cross platform font ascent/descents!

@srod,

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

Thank you for sharing. :D

Author:  RASHAD [ Wed Jan 16, 2019 6:18 pm ]
Post subject:  Re: Cross platform font ascent/descents!

Hi srod
Simple using only the VectorLib

Code:
  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

Author:  srod [ Wed Jan 16, 2019 6:30 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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.

Author:  RASHAD [ Wed Jan 16, 2019 6:43 pm ]
Post subject:  Re: Cross platform font ascent/descents!

Got it
Thanks
Any way I archived it (I knew that srod can not do it for nothing :) )

Author:  skywalk [ Wed Jan 16, 2019 6:47 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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)

Author:  srod [ Wed Jan 16, 2019 7:02 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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.

Author:  RASHAD [ Wed Jan 16, 2019 7:12 pm ]
Post subject:  Re: Cross platform font ascent/descents!

@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

Author:  srod [ Wed Jan 16, 2019 7:20 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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:
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.

Author:  RASHAD [ Wed Jan 16, 2019 7:44 pm ]
Post subject:  Re: Cross platform font ascent/descents!

Using Font Base Line as origin
Code:
  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

Author:  srod [ Wed Jan 16, 2019 8:13 pm ]
Post subject:  Re: Cross platform font ascent/descents!

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.

Author:  RASHAD [ Wed Jan 16, 2019 9:31 pm ]
Post subject:  Re: Cross platform font ascent/descents!

Workaround
Code:
   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

Author:  srod [ Thu Jan 17, 2019 12:22 am ]
Post subject:  Re: Cross platform font ascent/descents!

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.

Page 1 of 1 All times are UTC + 1 hour
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/