Module FontExtensions
''' <summary>
''' Retrieves the raw outline data for a given glyph from the specified <see cref="System.Drawing.Font"/>.
''' <para></para>
''' This function calls <see cref="DevCase.Win32.NativeMethods.GetGlyphOutline"/> in background
''' to retrieve outline data with the requested <paramref name="format"/>.
''' </summary>
'''
''' <param name="font">
''' The <see cref="System.Drawing.Font"/> object from which the glyph will be obtained.
''' </param>
'''
''' <param name="ch">
''' The character whose glyph outline will be requested.
''' </param>
'''
''' <param name="format">
''' The format in which the glyph outline will be retrieved.
''' <para></para>
''' This value only can be <see cref="GetGlyphOutlineFormat.Native"/> or <see cref="GetGlyphOutlineFormat.Bezier"/>.
''' <para></para>
''' Note: callers must interpret the returned byte array based on the selected format.
''' </param>
'''
''' <param name="matrix">
''' An optional <see cref="GlyphOutlineMatrix2"/> used to transform the glyph outline.
''' <para></para>
''' If no value is provided or default structure is passed, an identity matrix
''' will be used (see: <see cref="GlyphOutlineMatrix2.GetIdentityMatrix()"/>),
''' where the transfromed graphical object is identical to the source object.
''' </param>
'''
''' <returns>
''' A <see cref="Byte"/> array containing the raw glyph outline data with the requested <paramref name="format"/>.
''' <para></para>
''' Returns <see langword="Nothing"/> if the glyph is empty in the specified <paramref name="font"/>.
''' </returns>
'''
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="font"/> is <see langword="Nothing"/>.
''' </exception>
'''
''' <exception cref="ArgumentException">
''' Thrown when the specified <paramref name="format"/> is invalid to request glyph outline data.
''' </exception>
'''
''' <exception cref="System.ComponentModel.Win32Exception">
''' Thrown when a Win32 error occurs during font or device context operations.
''' </exception>
<Extension>
<EditorBrowsable(EditorBrowsableState.Always)>
<DebuggerStepThrough>
Public Function GetGlyphOutlineData(font As Font, ch As Char, format As GetGlyphOutlineFormat,
Optional matrix As GlyphOutlineMatrix2 = Nothing) As Byte()
If font Is Nothing Then
Throw New ArgumentNullException(paramName:=NameOf(font))
End If
If format <> GetGlyphOutlineFormat.Native AndAlso
format <> GetGlyphOutlineFormat.Bezier Then
Dim msg As String = $"The specified format '{format}' does not produce glyph outline data. " & Environment.NewLine &
$"Use '{NameOf(GetGlyphOutlineFormat.Native)}' or '{NameOf(GetGlyphOutlineFormat.Bezier)}' " &
"formats to request glyph outline data."
Throw New ArgumentException(msg, paramName:=NameOf(format))
End If
Dim hdc As IntPtr
Dim hFont As IntPtr
Dim oldObj As IntPtr
Dim win32Err As Integer
Try
hFont = font.ToHfont()
hdc = NativeMethods.CreateCompatibleDC(IntPtr.Zero)
oldObj = NativeMethods.SelectObject(hdc, hFont)
win32Err = Marshal.GetLastWin32Error()
If oldObj = IntPtr.Zero OrElse oldObj = DevCase.Win32.Common.Constants.HGDI_ERROR Then
Throw New Win32Exception(win32Err)
End If
Dim chCode As UInteger = CUInt(Convert.ToInt32(ch))
If matrix.Equals(New GlyphOutlineMatrix2()) Then
matrix = GlyphOutlineMatrix2.GetIdentityMatrix()
End If
Dim needed As UInteger = NativeMethods.GetGlyphOutline(hdc, chCode, format, Nothing, Nothing, Nothing, matrix)
win32Err = Marshal.GetLastWin32Error()
Select Case needed
Case 0UI
' Zero curve data points were returned, meaning the glyph is empty.
Return Nothing
Case DevCase.Win32.Common.Constants.GDI_ERROR
If win32Err = Win32ErrorCode.ERROR_SUCCESS Then
' The function returned GDI_ERROR, but no error recorded by GetLastError, meaning the function succeeded.
' Tests carried out have shown that when this happens the glyph simply does not exists.
Return Nothing
Else
Throw New Win32Exception(win32Err)
End If
Case Else
Dim bufferPtr As IntPtr = Marshal.AllocHGlobal(New IntPtr(needed))
Try
Dim got As UInteger = NativeMethods.GetGlyphOutline(hdc, chCode, format, Nothing, needed, bufferPtr, matrix)
win32Err = Marshal.GetLastWin32Error()
If got = DevCase.Win32.Common.Constants.GDI_ERROR AndAlso
win32Err <> Win32ErrorCode.ERROR_SUCCESS Then
Throw New Win32Exception(win32Err)
End If
Dim result(CInt(got) - 1) As Byte
Marshal.Copy(bufferPtr, result, 0, CInt(got))
Return result
Finally
Marshal.FreeHGlobal(bufferPtr)
End Try
End Select
Finally
If hFont <> IntPtr.Zero Then
NativeMethods.DeleteObject(hFont)
End If
If oldObj <> IntPtr.Zero Then
NativeMethods.DeleteObject(oldObj)
End If
If hdc <> IntPtr.Zero Then
NativeMethods.DeleteDC(hdc)
End If
End Try
End Function
''' <summary>
''' Determines whether the glyph outline for the specified character in the source <see cref="System.Drawing.Font"/>
''' is identical to the glyph outline of the same character in another <see cref="System.Drawing.Font"/>.
''' </summary>
'''
''' <param name="firstFont">
''' The first <see cref="System.Drawing.Font"/> to compare.
''' </param>
'''
''' <param name="secondFont">
''' The second <see cref="System.Drawing.Font"/> to compare.
''' </param>
'''
''' <param name="ch">
''' The character whose glyph outline will be compared between the two fonts.
''' </param>
'''
''' <returns>
''' <see langword="True"/> if both fonts produce identical outlines for the specified glyph.
''' <para></para>
''' <see langword="False"/> if the outlines differ or if one of the fonts has an empty glyph.
''' If the glyph outlines are empty in both fonts, returns <see langword="True"/>.
''' </returns>
<Extension>
<EditorBrowsable(EditorBrowsableState.Always)>
<DebuggerStepThrough>
Public Function GlyphOutlinesAreEqual(firstFont As Font, secondFont As Font, ch As Char) As Boolean
Dim firstBytes As Byte() = FontExtensions.GetGlyphOutlineData(firstFont, ch, GetGlyphOutlineFormat.Native)
Dim secondBytes As Byte() = FontExtensions.GetGlyphOutlineData(secondFont, ch, GetGlyphOutlineFormat.Native)
Return (firstBytes Is Nothing AndAlso secondBytes Is Nothing) OrElse
(
(firstBytes Is Nothing = (secondBytes Is Nothing)) AndAlso
firstBytes.SequenceEqual(secondBytes)
)
End Function
''' <summary>
''' Computes a similarity score between the glyph outline for the
''' specified character in the source <see cref="System.Drawing.Font"/>,
''' and the the glyph outline of the same character in another <see cref="System.Drawing.Font"/>.
''' </summary>
'''
''' <param name="firstFont">
''' The first <see cref="System.Drawing.Font"/> to compare.
''' </param>
'''
''' <param name="secondFont">
''' The second <see cref="System.Drawing.Font"/> to compare.
''' </param>
'''
''' <param name="ch">
''' The character whose glyph outlines will be compared between the two fonts.
''' </param>
'''
''' <returns>
''' A <see cref="Single"/> value between 0.0 and 1.0 representing the similarity
''' (the number of matching bytes in the outline data) of the glyph outlines.
''' <para></para>
''' If one of the fonts has an empty glyph, returns 0. If the glyph outlines are empty in both fonts, returns 1.
''' </returns>
<Extension>
<EditorBrowsable(EditorBrowsableState.Always)>
<DebuggerStepThrough>
Public Function GetGlyphOutlineSimilarity(firstFont As Font, secondFont As Font, ch As Char) As Single
Dim firstBytes As Byte() = FontExtensions.GetGlyphOutlineData(firstFont, ch, GetGlyphOutlineFormat.Native)
Dim secondBytes As Byte() = FontExtensions.GetGlyphOutlineData(secondFont, ch, GetGlyphOutlineFormat.Native)
If firstBytes Is Nothing AndAlso secondBytes Is Nothing Then
Return 1.0F
End If
If (firstBytes Is Nothing) <> (secondBytes Is Nothing) Then
Return 0.0F
End If
Dim maxLength As Integer = System.Math.Max(firstBytes.Length, secondBytes.Length)
Dim minLength As Integer = System.Math.Min(firstBytes.Length, secondBytes.Length)
Dim equalCount As Integer = 0
For i As Integer = 0 To minLength - 1
If firstBytes(i) = secondBytes(i) Then
equalCount += 1
End If
Next
Return CSng(equalCount) / maxLength
End Function
End Module