讀 WPF 源代碼 了解獲取 GlyphTypeface 的 CharacterToGlyphMap 的數量耗時原因
在 WPF 里面的 GlyphTypeface 表示字體的字形信息,通過 GlyphTypeface.CharacterToGlyphMap 屬性可以將給定的字符映射到字形索引,這個屬性是一個 IDictionary<int, ushort> 結構,其定義如下
public class GlyphTypeface : ITypefaceMetrics, ISupportInitialize
{
...
public IDictionary<int, ushort> CharacterToGlyphMap
{
get
{
CheckInitialized(); // This can only be called on fully initialized GlyphTypeface
return _fontFace.CharacterMap;
}
}
private FontFaceLayoutInfo _fontFace;
...
}
咋看起來沒問題,實際調用的時候,通過 CharacterToGlyphMap 的 TryGetValue 方法獲取傳入的字符對應的字形索引也是十分快速,沒有什么耗時。唯獨只有調用 CharacterToGlyphMap.Count 屬性時,才能測量到很大的耗時
這是為什么呢,讓咱繼續點開 _fontFace.CharacterMap 屬性的定義,其代碼如下
internal sealed class FontFaceLayoutInfo
{
...
internal IntMap CharacterMap
{
get
{
return _cmap;
}
}
...
}
可見是返回了 IntMap 類型的 _cmap 字段,看到這里,似乎非常清真
繼續看看 IntMap 類型的實現
internal sealed class IntMap : IDictionary<int, ushort>
{
...
public int Count
{
get { return CMap.Count; }
}
private Dictionary<int, ushort> CMap
{
...
}
...
}
看起來其中核心就在于 CMap 的 get 函數里,繼續看看此屬性的實現
internal sealed class IntMap : IDictionary<int, ushort>
{
...
private Dictionary<int, ushort> CMap
{
get
{
if (_cmap == null)
{
lock (this)
{
if (_cmap == null)
{
_cmap = new Dictionary<int, ushort>();
ushort glyphIndex;
for (int codePoint = 0; codePoint <= FontFamilyMap.LastUnicodeScalar; ++codePoint)
{
if (TryGetValue(codePoint, out glyphIndex))
{
_cmap.Add(codePoint, glyphIndex);
}
}
}
}
}
return _cmap;
}
}
private Dictionary<int, ushort> _cmap;
...
}
public class FontFamilyMap
{
...
internal const int LastUnicodeScalar = 0x10ffff;
...
}
可以看到,此時如果調用 CharacterToGlyphMap.Count 屬性,將會導致 _cmap 字段被初始化。而初始化的過程是采用一個巨大的循環,足足有 0x10ffff 的百萬次循環調用 TryGetValue 方法創建的字典
即使 TryGetValue 方法速度再快,但是循環本身將會調用 1114111 (0x10ffff) 百萬次,這就是耗時的原因
按照當前的代碼,直接調用 CharacterToGlyphMap.Count 屬性是非常虧的,將會導致 _cmap 字段被初始化,初始化此字典需要經過百萬次的循環,自然性能很差。但很顯然,這是一個很好做的優化點,只需要繞開字典初始化,直接獲取數量即可
既然看起來這是一個很好的優化點,自然我就將其優化了: https://github.com/dotnet/wpf/pull/11139
對于業務端開發者,等不及 WPF 的更新的伙伴們,可以直接使用 GlyphTypeface.GlyphCount 屬性代替 CharacterToGlyphMap.Count 屬性即可
public class GlyphTypeface : ITypefaceMetrics, ISupportInitialize
{
...
public int GlyphCount
{
get
{
CheckInitialized(); // This can only be called on fully initialized GlyphTypeface
int glyphCount;
MS.Internal.Text.TextInterface.FontFace fontFaceDWrite = _font.GetFontFace();
try
{
glyphCount = fontFaceDWrite.GlyphCount;
}
finally
{
fontFaceDWrite.Release();
}
return glyphCount;
}
}
...
}
通過以上代碼可見 GlyphCount 屬性是直接獲取的,基本測量不出耗時
博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請訪問 https://blog.lindexi.com/
如圖片看不見,請在瀏覽器開啟不安全http內容兼容

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://www.rzrgm.cn/lindexi)(包含鏈接:http://www.rzrgm.cn/lindexi ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布。如有任何疑問,請與我[聯系](mailto:lindexi_gd@163.com)。

浙公網安備 33010602011771號