0
votes

Background: I am trying to render some text using the Cambria Math font. In order to position and scale it to fit correctly I need to measure the text. However both Graphics.MeasureString and Graphics.MeasureCharacterRanges seem to return the line height of the text defined in the FontMetrics for the font, regardless of the actual text to be rendered. For example if I measure a simple single character such as "x", it is reported to be nearly 8 times as tall as it is wide.

This particular font has a very large ascent, descent and line height, presumably because it contains a few unusual symbols which are very tall. (FontMetrics.GetLineHeight is 11428 for this font, as opposed to 2318 for a random, typical font I tested. Both had GetEmHeight = 2048).

The effects of this can be seen in the Character map application in Win7: if you select the Cambria Math font, and select a character it appears as a large dot in the selection box - presumably it has been scaled to about 15% of natural size to make the theoretical line height fit the box.

The question: Is there a way to measure the actual height of a particular string, which would give me a more meaningful height in this case? In practice I won't be rendering any unusually tall characters.

2
Not sure of the relevance of that Hans? I know that the cell ascent is enormous for this font - that's the problem - it's several times larger than the visual ascent of almost all the characters in the font. I'm looking for a measurement for specific characters which doesn't use the misleading ascent and descent from the FontFamily.Stuart Whitehouse
Thanks I hadn't seen that one - I was searching on the measurement question rather than the font name. It's the same problem, but somewhat different question - he wants to assign the font to a WinForms control, whereas if I can measure a given set of characters I can render correctly. I suspect the answer - "you can't" - may be the same however.Stuart Whitehouse

2 Answers

1
votes

I know this was posted a year ago, and assume you've either fixed without posting your answer, or gave up.

Anyway, this is some code I've just written. The topline string is just so I've got letters with a "drop" (j and g) and some that are the tallest (any caps in most fonts, I guess).

The result is a size in pixels, which you'll then need to scale using whatever device resolution you need

    Dim tmpfont As Font = New Font(FontFamily.GenericSansSerif, 32)

    Dim supportedcharheight As String = "TKjg,'"
    Dim tmpsize As SizeF = _labelcanvas.MeasureString(supportedcharheight, tmpfont)
    Dim b As Bitmap = New Bitmap(tmpsize.Width, tmpsize.Height)

    Dim g As Graphics = Graphics.FromImage(b)
    g.FillRectangle(Brushes.White, 0, 0, b.Width, b.Height)
    g.DrawString(supportedcharheight, tmpfont, Brushes.Black, 0, 0)
    g.Dispose()

    Dim d As Drawing.Imaging.BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)


    Dim pixel(3) As Byte
    Dim rowptr As IntPtr
    Dim pixptr As IntPtr

    Dim toprow As Integer = b.Height
    Dim bottomrow As Integer = 0
    Dim foundpix As Boolean = False

    For y As Integer = 0 To b.Height - 1
        rowptr = d.Scan0 + (y * d.Stride)
        For x As Integer = 0 To b.Width - 1
            pixptr = rowptr + (x * 4)
            Runtime.InteropServices.Marshal.Copy(pixptr, pixel, 0, 4)
            If pixel(0) < 255 Then
                If toprow > y Then toprow = y
                If bottomrow < y Then bottomrow = y
                Exit For
            End If
        Next
    Next

    b.UnlockBits(d)
    b.Dispose()

    Dim actualpixels As Integer = bottomrow - toprow
1
votes

Marq's answer has prompted me to answer with another method for measuring characters that I've since used elsewhere, that is probably more efficient. It's possible to get the full path data for a string using the FontFamily (not Font!) object. And from that the exact metrics can easily be obtained:

Dim path As New Drawing2D.GraphicsPath
path.AddString(myText, myFontFamily,....)
Dim textSize As SizeF = path.GetBounds.Size

That measures the exact size occupied by the actual characters. By playing with the layout rectangle in AddString, you could also measure the dead space on each of the 4 sides, etc.