Hi,
I have created a class which is a "font searcher" the idea being that it will detect the font required for a given string in any language by getting each available font's unicode range(s) and comparing those ranges to the ones found in the string. This is working perfectly except that on the 3rd/4th time the code is run (without re-starting IIS) i get an out of memory exception.
Here is the class:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Text;
using PdfSharp.Drawing;
namespace PRT.Common
{
public class FontSearcher
{
#region Imports
[DllImport("gdi32.dll")]
public static extern uint GetFontUnicodeRanges(IntPtr hdc, IntPtr lpgs);
[DllImport("gdi32.dll")]
public extern static IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
#endregion
#region Class Vars
private List<string> __PreferredFontNames;
private FontInfoList __PreferredFontInfos;
private FontInfoList __NonPreferredFontInfos;
List<FontInfoList> __FiLists = new List<FontInfoList>();
#endregion
#region Constructor
public FontSearcher(List<string> preferredFontNames)
{
__PreferredFontNames = preferredFontNames;
BuildFontInfos();
__FiLists = new List<FontInfoList>();
__FiLists.Add(__PreferredFontInfos);
__FiLists.Add(__NonPreferredFontInfos);
}
#endregion
#region Methods...
#region Private Members
private void BuildFontInfos()
{
#region Vars Setup
__PreferredFontInfos = new FontInfoList();
__NonPreferredFontInfos = new FontInfoList();
InstalledFontCollection fonts = new InstalledFontCollection();
#endregion
#region Build FontInfo Objects
for (int i = 0; i < fonts.Families.Length; i++)
{
try
{
Font _font = null;
if (fonts.Families[i].IsStyleAvailable(FontStyle.Regular))
_font = new Font(fonts.Families[i].Name, 10, FontStyle.Regular);
else if (fonts.Families[i].IsStyleAvailable(FontStyle.Bold))
_font = new Font(fonts.Families[i].Name, 10, FontStyle.Bold);
else if (fonts.Families[i].IsStyleAvailable(FontStyle.Italic))
_font = new Font(fonts.Families[i].Name, 10, FontStyle.Italic);
if (__PreferredFontNames.Contains(_font.Name))
{
try
{
__PreferredFontInfos.Add(new FontInfo(_font, GetUnicodeRangesForFont(_font)));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
else
{
try
{
__NonPreferredFontInfos.Add(new FontInfo(_font, GetUnicodeRangesForFont(_font)));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
#endregion
#region Re-Order __PreferredFontInfos to match the order of __PreferredFontNames
FontInfoList __OrderedPreferedFontInfos = new FontInfoList();
int d = 0;
while (__OrderedPreferedFontInfos.Count < __PreferredFontInfos.Count)
{
for (int i = 0; i < __PreferredFontInfos.Count; i++)
{
if (__PreferredFontInfos[i].Font.Name == __PreferredFontNames[d])
{
__OrderedPreferedFontInfos.Add(__PreferredFontInfos[i]);
}
}
d++;
}
__PreferredFontInfos = __OrderedPreferedFontInfos;
#endregion
}
private List<FontRange> GetUnicodeRangesForFont(Font font)
{
Graphics g = Graphics.FromHwnd(IntPtr.Zero);
IntPtr hdc = g.GetHdc();
IntPtr hFont = font.ToHfont();
IntPtr old = SelectObject(hdc, hFont);
uint size = GetFontUnicodeRanges(hdc, IntPtr.Zero);
IntPtr glyphSet = Marshal.AllocHGlobal((int)size);
GetFontUnicodeRanges(hdc, glyphSet);
List<FontRange> fontRanges = new List<FontRange>();
int count = Marshal.ReadInt32(glyphSet, 12);
for (int i = 0; i < count; i++)
{
FontRange range = new FontRange();
range.Low = (UInt16)Marshal.ReadInt16(glyphSet, 16 + i * 4);
range.High = (UInt16)(range.Low + Marshal.ReadInt16(glyphSet, 18 + i * 4) - 1);
fontRanges.Add(range);
}
SelectObject(hdc, old);
Marshal.FreeHGlobal(glyphSet);
g.ReleaseHdc(hdc);
g.Dispose();
return fontRanges;
}
private bool CheckIfCharInFontInfo(char character, FontInfo fontInfo)
{
UInt16 intval = Convert.ToUInt16(character);
List<FontRange> ranges = fontInfo.Ranges;
bool isCharacterPresent = false;
foreach (FontRange range in ranges)
{
if (intval >= range.Low && intval <= range.High)
{
isCharacterPresent = true;
break;
}
}
return isCharacterPresent;
}
private FontInfo CheckBestFont(List<FontInfo> fontInfos, string str)
{
#region Vars Setup
FontInfo _fontInfo = null;
List<int> _occs = new List<int>();
int _occ = 0;
#endregion
#region Build List containing number of matching glyphs contained within each FontInfo
for (int i = 0; i < fontInfos.Count; i++)
{
for (int c = 0; c < str.Length; c++)
{
if (CheckIfCharInFontInfo(str[c], fontInfos[i]))
{
_occ++;
}
}
_occs.Add(_occ);
_occ = 0;
}
#endregion
#region Get the FontInfo with the highest number of glyph matches
if (_occs.Count > 0)
{
int _fiOcc = _occs[0];
int _fiIdx = 0;
for (int i = 1; i < _occs.Count; i++)
{
if (_occs[i] > _fiOcc)
{
_fiOcc = _occs[i];
_fiIdx = i;
}
}
_fontInfo = fontInfos[_fiIdx];
if (!__PreferredFontInfos.Contains(_fontInfo))
{
__PreferredFontInfos.Add(_fontInfo);
__NonPreferredFontInfos.Remove(_fontInfo);
}
}
#endregion
return _fontInfo;
}
private FontInfo CheckBestFont(List<FontInfo> fontInfos, string str, string preferredFontName)
{
#region Vars Setup
FontInfo _fontInfo = null;
List<int> _occs = new List<int>();
int _occ = 0;
#endregion
#region Build List containing number of matching glyphs contained within each FontInfo
for (int i = 0; i < fontInfos.Count; i++)
{
for (int c = 0; c < str.Length; c++)
{
if (CheckIfCharInFontInfo(str[c], fontInfos[i]))
{
_occ++;
}
}
_occs.Add(_occ);
_occ = 0;
}
#endregion
#region Get the FontInfo with the highest number of glyph matches
if (_occs.Count > 0)
{
int _fiOcc = _occs[0];
int _fiIdx = 0;
for (int i = 1; i < _occs.Count; i++)
{
if(_occs[i] > 0 && fontInfos[i].Font.Name == preferredFontName)
{
_fiIdx = i;
break;
}
if (_occs[i] > _fiOcc)
{
_fiOcc = _occs[i];
_fiIdx = i;
}
}
_fontInfo = fontInfos[_fiIdx];
if (!__PreferredFontInfos.Contains(_fontInfo))
{
__PreferredFontInfos.Add(_fontInfo);
__NonPreferredFontInfos.Remove(_fontInfo);
}
}
#endregion
return _fontInfo;
}
private string StripTextFormattingChars(string str)
{
str = str.Replace("\r", "");
str = str.Replace("\t", "");
str = str.Replace("\n", "");
return str;
}
#endregion
#region Public Members
public FontInfo GetFontInfoFromChar(char character)
{
FontInfo _fontInfo = null;
int fi = 0;
while (fi < __FiLists.Count && _fontInfo == null)
{
for (int i = 0; i < __FiLists[fi].Count; i++)
{
if (CheckIfCharInFontInfo(character, __FiLists[fi][i]))
{
_fontInfo = __FiLists[fi][i];
break;
}
}
fi++;
}
return _fontInfo;
}
public FontInfo GetFontInfoFromChar(char character, string preferredFontName)
{
FontInfo _fontInfo = null;
int fi = 0;
while (fi < __FiLists.Count && _fontInfo == null)
{
for (int i = 0; i < __FiLists[fi].Count; i++)
{
if (__FiLists[fi][i].Font.Name == preferredFontName)
{
if (CheckIfCharInFontInfo(character, __FiLists[fi][i]))
{
_fontInfo = __FiLists[fi][i];
break;
}
}
}
if (_fontInfo == null)
{
for (int i = 0; i < __FiLists[fi].Count; i++)
{
if (__FiLists[fi][i].Font.Name == preferredFontName)
{
}
if (CheckIfCharInFontInfo(character, __FiLists[fi][i]))
{
_fontInfo = __FiLists[fi][i];
break;
}
}
}
fi++;
}
return _fontInfo;
}
public FontInfo GetFontInfoFromChar(char character, List<FontInfo> fontInfos)
{
FontInfo _fontInfo = null;
for (int i = 0; i < fontInfos.Count; i++)
{
if (CheckIfCharInFontInfo(character, fontInfos[i]))
{
_fontInfo = fontInfos[i];
break;
}
}
return _fontInfo;
}
public FontInfo GetFontInfoFromString(string str)
{
#region Vars Setup
str = StripTextFormattingChars(str);
List<FontInfo> _fontsInString = new List<FontInfo>();
#endregion
#region Build List of Fonts required by str
for (int i = 0; i < str.Length; i++)
{
char _c = str[i];
FontInfo _charFI = GetFontInfoFromChar(_c);
if (_charFI != null)
{
if (_fontsInString.Count > 0)
{
if (!_fontsInString.Contains(_charFI))
{
_fontsInString.Add(_charFI);
}
}
else
{
_fontsInString.Add(_charFI);
}
}
}
#endregion
#region Select the Font to use
if (_fontsInString.Count > 0)
{
if (_fontsInString.Count > 1)
{
FontInfo _fontInfo = CheckBestFont(_fontsInString, str);
return _fontInfo;
}
else
{
return _fontsInString[0];
}
}
else
{
return new FontInfo(new Font("Arial", 10, FontStyle.Regular), null);
}
#endregion
}
public FontInfo GetFontInfoFromString(string str, string preferredFontName)
{
#region Vars Setup
str = StripTextFormattingChars(str);
List<FontInfo> _fontsInString = new List<FontInfo>();
#endregion
#region Build List of Fonts required by str
for (int i = 0; i < str.Length; i++)
{
char _c = str[i];
FontInfo _charFI = GetFontInfoFromChar(_c, preferredFontName);
if (_charFI != null)
{
if (_fontsInString.Count > 0)
{
if (!_fontsInString.Contains(_charFI))
{
_fontsInString.Add(_charFI);
}
}
else
{
_fontsInString.Add(_charFI);
}
}
}
#endregion
#region Select the Font to use
if (_fontsInString.Count > 0)
{
if (_fontsInString.Count > 1)
{
FontInfo _fontInfo = CheckBestFont(_fontsInString, str, preferredFontName);
return _fontInfo;
}
else
{
return _fontsInString[0];
}
}
else
{
return new FontInfo(new Font("Arial", 10, FontStyle.Regular), null);
}
#endregion
}
public bool ClearAll()
{
try
{
__PreferredFontNames.Clear();
//__PreferredFontNames = null;
__PreferredFontInfos.Clear();
//__PreferredFontInfos = null;
__NonPreferredFontInfos.Clear();
//__NonPreferredFontInfos = null;
__FiLists.Clear();
//__FiLists = null;
GC.Collect();
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
#endregion
#endregion
#region Properties
public FontInfoList NonPreferredFontInfos
{
get
{
return __NonPreferredFontInfos;
}
set
{
__NonPreferredFontInfos = value;
}
}
public FontInfoList PreferredFontInfos
{
get
{
return __PreferredFontInfos;
}
set
{
__PreferredFontInfos = value;
}
}
#endregion
}
#region FontInfo Class
public class FontInfo
{
#region Class Vars
private Font __Font;
private List<FontRange> __Ranges;
#endregion
#region Constructor
public FontInfo(Font font, List<FontRange> ranges)
{
__Font = font;
__Ranges = ranges;
}
#endregion
#region Properties
public Font Font
{
get
{
return __Font;
}
}
public List<FontRange> Ranges
{
get
{
return __Ranges;
}
}
#endregion
}
#endregion
#region FontInfoList Class
public class FontInfoList : List<FontInfo>
{
#region Constructor
public FontInfoList()
{
}
#endregion
#region Methods
public FontInfo GetFontInfoByFont(Font font)
{
FontInfo _fi = null;
for (int i = 0; i < this.Count; i++)
{
if (this[i].Font == font)
{
_fi = this[i];
break;
}
}
return _fi;
}
#endregion
}
#endregion
#region FontRange struct
public struct FontRange
{
public UInt16 Low;
public UInt16 High;
}
#endregion
}
In the constructor i pass in a list of font names which are "preferred" to be used, so the font class looks to see if the unicode ranges in the string match those first, otherwise, it will search in the "NonPreferredFontInfos" list (which is all other fonts installed on the system) for the unicode ranges.
I use this class when creating PDF's using the PDFSharp framework. on the entry point aspx page I call "FontSearcher.ClearAll()" in the Page.Unload event handler which clears the lists, and then within the ClearAll() function I call GC.Collect() - i know this GC.Collect() function is far from perfect, but i dont know of another approach - really i shouldnt need it as the automatic garbage collection should do the trick, but it didnt, hence why i am calling CG.Collect().
I can solve it by re-starting IIS every time it occurs, but as im sure you can guess, this is a far from perfect solution.
Hopefully i have covered everything, but if I have missed something out, please say!
thanks,
Mike