I haven't found a default font for a given script yet but at least a way to figure out whether a font supports a given script. Starting from Windows Vista, Uniscribe (linked from @Jimi's answer to a similar question) and in particular the ScriptGetFontScriptTags
function can be used to query information about OpenType fonts.
Using a little P/Invoke we could do:
private const int MaximumTagCount = 32;
private static int[] GetScriptTags(IntPtr context, Font font)
{
IntPtr oldFont = SelectObject(context, font.ToHfont());
if (oldFont == IntPtr.Zero)
throw new Win32Exception();
IntPtr scriptCache = IntPtr.Zero;
IntPtr tagsPointer = IntPtr.Zero;
IntPtr tagCountPointer = IntPtr.Zero;
try
{
// uniscribe expects a pointer to a SCRIPT_CACHE pointer
scriptCache = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(scriptCache, IntPtr.Zero);
tagsPointer = Marshal.AllocHGlobal(4 * MaximumTagCount); // one tag is 4 bytes long
tagCountPointer = Marshal.AllocHGlobal(4);
int status = ScriptGetFontScriptTags(context, scriptCache, IntPtr.Zero, MaximumTagCount, tagsPointer, tagCountPointer);
if (status != 0)
throw new Win32Exception(status);
int tagCount = Marshal.ReadInt32(tagCountPointer);
if (tagCount > 0 && tagCount <= MaximumTagCount)
{
int[] tags = new int[tagCount];
for (int i = 0; i < tagCount; i++)
tags[i] = Marshal.ReadInt32(tagsPointer, 4 * i);
return tags;
}
else
{
return new int[0];
}
}
finally
{
SelectObject(context, oldFont);
if (scriptCache != IntPtr.Zero)
{
ScriptFreeCache(scriptCache);
Marshal.FreeHGlobal(scriptCache);
}
if (tagsPointer != IntPtr.Zero)
Marshal.FreeHGlobal(tagsPointer);
if (tagCountPointer != IntPtr.Zero)
Marshal.FreeHGlobal(tagCountPointer);
}
}
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr value);
[DllImport("usp10.dll")]
private static extern int ScriptGetFontScriptTags(IntPtr hdc, IntPtr scriptCache, IntPtr scriptAnalysis, int maxTags, IntPtr tags, IntPtr tagCount);
[DllImport("usp10.dll")]
private static extern int ScriptFreeCache(IntPtr scriptCache);
which we could then use once we have a device context from a graphics object:
class Form1 : Form
{
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
using (var g = Graphics.FromHwnd(Handle))
{
IntPtr context = g.GetHdc();
try
{
foreach (FontFamily family in FontFamily.Families)
{
using (var font = new Font(family, 12))
{
Console.WriteLine(font);
Console.Write(" Scripts:");
foreach (int script in GetScriptTags(context, font))
{
Console.Write(" {0} ({1:x})", TagToString(script), script);
}
Console.WriteLine();
}
}
}
finally
{
g.ReleaseHdc(context);
}
}
}
private static string TagToString(int script)
{
var bytes = BitConverter.GetBytes(script);
var builder = new StringBuilder(bytes.Length);
foreach (var b in bytes)
builder.Append((char)b);
return builder.ToString();
}
}
Now all we have to do is check whether a font has the latn
(or 0x6e74616c
as a little endian integer) script tag. Some examples:
[Font: Name=楷体, Size=12, Units=3, GdiCharSet=1, GdiVerticalFont=False]
Scripts: hani (696e6168)
[Font: Name=Microsoft Himalaya, Size=12, Units=3, GdiCharSet=1, GdiVerticalFont=False]
Scripts: tibt (74626974)
[Font: Name=Microsoft JhengHei Light, Size=12, Units=3, GdiCharSet=1, GdiVerticalFont=False]
Scripts: hani (696e6168) kana (616e616b) latn (6e74616c)
[Font: Name=Microsoft Sans Serif, Size=12, Units=3, GdiCharSet=1, GdiVerticalFont=False]
Scripts: arab (62617261) hebr (72626568) latn (6e74616c) thai (69616874) cyrl (6c727963)
[Font: Name=Segoe UI, Size=12, Units=3, GdiCharSet=1, GdiVerticalFont=False]
Scripts: arab (62617261) bng2 (32676e62) cyrl (6c727963) dev2 (32766564) gjr2 (32726a67) grek (6b657267) gur2 (32727567) hebr (72626568) khmr (726d686b) knd2 (32646e6b) lao (206f616c) latn (6e74616c) mlm2 (326d6c6d) mong (676e6f6d) mymr (726d796d) ory2 (3279726f) talu (756c6174) tel2 (326c6574) thai (69616874) tibt (74626974) tml2 (326c6d74) beng (676e6562) deva (61766564) gujr (726a7567) guru (75727567) knda (61646e6b) mlym (6d796c6d) orya (6179726f) taml (6c6d6174) telu (756c6574)