/// <summary> /// Merges two character sets to form a new one that contains both of their characters without duplicates. /// </summary> /// <param name="second"></param> /// <returns></returns> public FontCharSet MergedWith(FontCharSet second) { return new FontCharSet( chars: MergeCharList(this.chars, (second != null) ? second.chars : null), charBaseLineRef: MergeCharList(this.charBaseLineRef, (second != null) ? second.charBaseLineRef : null), charDescentRef: MergeCharList(this.charDescentRef, (second != null) ? second.charDescentRef : null), charBodyAscentRef: MergeCharList(this.charBodyAscentRef, (second != null) ? second.charBodyAscentRef : null) ); }
/// <summary> /// Renders the <see cref="Duality.Resources.FontRasterizer"/> using the specified system font. /// This method assumes that the system font's size and style match the one specified in /// the specified Duality font. /// </summary> private FontData RenderGlyphs(SysDrawFont internalFont, FontCharSet charSet, FontRenderMode renderMode, bool antialiazing, bool monospace) { if (renderMode == FontRenderMode.ClearType) { return(RenderGlyphsCleartype(internalFont, charSet, monospace)); } FontGlyphData[] glyphs = new FontGlyphData[charSet.Chars.Length]; for (int i = 0; i < glyphs.Length; i++) { glyphs[i].Glyph = charSet.Chars[i]; } int bodyAscent = 0; int baseLine = 0; int descent = 0; int ascent = 0; TextRenderingHint textRenderingHint; if (antialiazing) { textRenderingHint = TextRenderingHint.AntiAliasGridFit; } else { textRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; } int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(glyphs.Length)); PixelData pixelLayer = new PixelData( MathF.RoundToInt(cols * internalFont.Size * 1.2f), MathF.RoundToInt(rows * internalFont.Height * 1.2f), ColorRgba.TransparentBlack); Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[glyphs.Length]; PixelData[] glyphBitmaps = new PixelData[glyphs.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { Brush fntBrush = new SolidBrush(Color.Black); StringFormat formatDef = StringFormat.GenericDefault; formatDef.LineAlignment = StringAlignment.Near; formatDef.FormatFlags = 0; StringFormat formatTypo = StringFormat.GenericTypographic; formatTypo.LineAlignment = StringAlignment.Near; int x = 1; int y = 1; for (int i = 0; i < glyphs.Length; ++i) { string str = glyphs[i].Glyph.ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, internalFont, pixelLayer.Width, formatDef); // Rasterize a single glyph for rendering Bitmap bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphBitmaps[i] = new PixelData(); glyphBitmaps[i].FromBitmap(bm); // Rasterize a single glyph in typographic mode for metric analysis PixelData glyphTempTypo; if (!isSpace) { Point2 glyphTempOpaqueTopLeft; Point2 glyphTempOpaqueSize; glyphBitmaps[i].GetOpaqueBoundaries(out glyphTempOpaqueTopLeft, out glyphTempOpaqueSize); glyphBitmaps[i].SubImage(glyphTempOpaqueTopLeft.X, 0, glyphTempOpaqueSize.X, glyphBitmaps[i].Height); if (charSet.CharBodyAscentRef.Contains(glyphs[i].Glyph)) { bodyAscent += glyphTempOpaqueSize.Y; } if (charSet.CharBaseLineRef.Contains(glyphs[i].Glyph)) { baseLine += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y; } if (charSet.CharDescentRef.Contains(glyphs[i].Glyph)) { descent += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y; } bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new PixelData(); glyphTempTypo.FromBitmap(bm); glyphTempTypo.Crop(true, false); } else { glyphTempTypo = glyphBitmaps[i]; } // Update xy values if it doesn't fit anymore if (x + glyphBitmaps[i].Width + 2 > pixelLayer.Width) { x = 1; y += internalFont.Height + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data glyphs[i].Size = glyphBitmaps[i].Size; glyphs[i].Offset.X = glyphBitmaps[i].Width - glyphTempTypo.Width; glyphs[i].Offset.Y = 0; // TTF fonts are rendered on blocks that are the whole size of the height - so no need for offset if (isSpace) { glyphs[i].Size.X /= 2; glyphs[i].Offset.X /= 2; } glyphs[i].Advance = glyphs[i].Size.X - glyphs[i].Offset.X; atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphBitmaps[i].Width; atlas[i].H = (internalFont.Height + 1); // Draw it onto the font surface glyphBitmaps[i].DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphBitmaps[i].Width + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.125f), 2, 10); } } // White out texture except alpha channel. for (int i = 0; i < pixelLayer.Data.Length; i++) { pixelLayer.Data[i].R = 255; pixelLayer.Data[i].G = 255; pixelLayer.Data[i].B = 255; } // Monospace offset and advance adjustments if (monospace) { float maxGlyphWidth = 0; for (int i = 0; i < glyphs.Length; i++) { maxGlyphWidth = Math.Max(maxGlyphWidth, glyphs[i].Size.X); } for (int i = 0; i < glyphs.Length; ++i) { glyphs[i].Offset.X -= (int)Math.Round((maxGlyphWidth - glyphs[i].Size.X) / 2.0f); glyphs[i].Advance = maxGlyphWidth; } } // Determine Font properties { float lineSpacing = internalFont.FontFamily.GetLineSpacing(internalFont.Style); float emHeight = internalFont.FontFamily.GetEmHeight(internalFont.Style); float cellAscent = internalFont.FontFamily.GetCellAscent(internalFont.Style); float cellDescent = internalFont.FontFamily.GetCellDescent(internalFont.Style); ascent = (int)Math.Round(cellAscent * internalFont.Size / emHeight); bodyAscent /= charSet.CharBodyAscentRef.Length; baseLine /= charSet.CharBaseLineRef.Length; descent = (int)Math.Round(((float)descent / charSet.CharDescentRef.Length) - (float)baseLine); } // Aggregate rendered and generated data into our return value FontMetrics metrics = new FontMetrics( size: internalFont.SizeInPoints, height: (int)internalFont.Height, ascent: ascent, bodyAscent: bodyAscent, descent: descent, baseLine: baseLine, monospace: monospace); // Determine kerning pairs FontKerningPair[] kerningPairs = null; if (monospace) { kerningPairs = null; } else { kerningPairs = this.GatherKerningPairs(glyphs, metrics, glyphBitmaps); } return(new FontData(pixelLayer, atlas, glyphs, metrics, kerningPairs)); }
private FontData RenderGlyphsCleartype(SysDrawFont internalFont, FontCharSet charSet, bool monospace) { FontGlyphData[] glyphs = new FontGlyphData[charSet.Chars.Length]; for (int i = 0; i < glyphs.Length; i++) { glyphs[i].Glyph = charSet.Chars[i]; } int bodyAscent = 0; int baseLine = 0; int descent = 0; int ascent = 0; int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(glyphs.Length)); IntPtr hFont = internalFont.ToHfont(); PixelData pixelLayer = new PixelData( MathF.RoundToInt(cols * internalFont.Size * 1.2f), MathF.RoundToInt(rows * internalFont.Height * 1.2f), ColorRgba.TransparentBlack); Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[glyphs.Length]; PixelData[] glyphBitmaps = new PixelData[glyphs.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { IntPtr measureHdc = measureGraphics.GetHdc(); int x = 1; int y = 1; for (int i = 0; i < glyphs.Length; ++i) { string str = glyphs[i].Glyph.ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; Size charSize; GetTextExtentPoint32(measureHdc, str, str.Length, out charSize); // Rasterize a single glyph for rendering Bitmap bm = new Bitmap(Math.Max(1, charSize.Width), internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(SystemColors.Window); IntPtr hdc = glyphGraphics.GetHdc(); SelectObject(hdc, hFont); SetTextColor(hdc, (0 /*B*/ << 16) | (0 /*G*/ << 8) | 0 /*R*/); TextOut(hdc, 0, 0, str, str.Length); glyphGraphics.ReleaseHdc(hdc); } glyphBitmaps[i] = new PixelData(); glyphBitmaps[i].FromBitmap(bm); PixelData glyphTempTypo = glyphBitmaps[i]; // Update xy values if it doesn't fit anymore if (x + glyphBitmaps[i].Width + 2 > pixelLayer.Width) { x = 1; y += internalFont.Height + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data glyphs[i].Size = glyphBitmaps[i].Size; glyphs[i].Offset.X = glyphBitmaps[i].Width - glyphTempTypo.Width; glyphs[i].Offset.Y = 0; // TTF fonts are rendered on blocks that are the whole size of the height - so no need for offset if (isSpace) { glyphs[i].Size.X /= 2; glyphs[i].Offset.X /= 2; } glyphs[i].Advance = glyphs[i].Size.X - glyphs[i].Offset.X; atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphBitmaps[i].Width; atlas[i].H = (internalFont.Height + 1); // Draw it onto the font surface glyphBitmaps[i].DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphBitmaps[i].Width + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.125f), 2, 10); } measureGraphics.ReleaseHdc(measureHdc); } // ToDo: finally DeleteObject(hFont); // Monospace offset and advance adjustments if (monospace) { float maxGlyphWidth = 0; for (int i = 0; i < glyphs.Length; i++) { maxGlyphWidth = Math.Max(maxGlyphWidth, glyphs[i].Size.X); } for (int i = 0; i < glyphs.Length; ++i) { glyphs[i].Offset.X -= (int)Math.Round((maxGlyphWidth - glyphs[i].Size.X) / 2.0f); glyphs[i].Advance = maxGlyphWidth; } } // Determine Font properties { float lineSpacing = internalFont.FontFamily.GetLineSpacing(internalFont.Style); float emHeight = internalFont.FontFamily.GetEmHeight(internalFont.Style); float cellAscent = internalFont.FontFamily.GetCellAscent(internalFont.Style); float cellDescent = internalFont.FontFamily.GetCellDescent(internalFont.Style); ascent = (int)Math.Round(cellAscent * internalFont.Size / emHeight); bodyAscent /= charSet.CharBodyAscentRef.Length; baseLine /= charSet.CharBaseLineRef.Length; descent = (int)Math.Round(((float)descent / charSet.CharDescentRef.Length) - (float)baseLine); } // Aggregate rendered and generated data into our return value FontMetrics metrics = new FontMetrics( size: internalFont.SizeInPoints, height: (int)internalFont.Height, ascent: ascent, bodyAscent: bodyAscent, descent: descent, baseLine: baseLine, monospace: monospace); // Determine kerning pairs FontKerningPair[] kerningPairs = null; if (monospace) { kerningPairs = null; } else { kerningPairs = this.GatherKerningPairs(glyphs, metrics, glyphBitmaps); } return(new FontData(pixelLayer, atlas, glyphs, metrics, kerningPairs)); }
/// <summary> /// Renders the <see cref="Duality.Resources.FontRasterizer"/> using the specified system font family. /// </summary> private FontData RenderGlyphs(FontFamily fontFamily, float emSize, FontStyle style, FontCharSet extendedSet, FontRenderMode renderMode, bool antialiasing, bool monospace) { // Determine System.Drawing font style SysDrawFontStyle systemStyle = SysDrawFontStyle.Regular; if (style.HasFlag(FontStyle.Bold)) { systemStyle |= SysDrawFontStyle.Bold; } if (style.HasFlag(FontStyle.Italic)) { systemStyle |= SysDrawFontStyle.Italic; } // Create a System.Drawing font SysDrawFont internalFont = null; if (fontFamily != null) { try { internalFont = new SysDrawFont(fontFamily, emSize, systemStyle); } catch (Exception e) { Console.WriteLine( "Failed to create System Font '{1} {2}, {3}' for rendering: {0}", /*LogFormat.Exception(*/ e /*)*/, fontFamily.Name, emSize, style); } } // If creating the font failed, fall back to a default one if (internalFont == null) { internalFont = new SysDrawFont(FontFamily.GenericMonospace, emSize, systemStyle); } // Render the font's glyphs using (internalFont) { return(this.RenderGlyphs( internalFont, FontCharSet.Default.MergedWith(extendedSet), renderMode, antialiasing, monospace)); } }
/// <summary> /// Renders the <see cref="Duality.Resources.FontRasterizer"/> based on its embedded TrueType representation. /// <param name="extendedSet">Extended set of characters for renderning.</param> /// </summary> private FontData RenderGlyphs(string fontFamily, float emSize, FontStyle style, FontCharSet extendedSet, FontRenderMode renderMode, bool antialiasing, bool monospace) { FontFamily family = new FontFamily(fontFamily); // Render the font's glyphs return(this.RenderGlyphs( family, emSize, style, extendedSet, renderMode, antialiasing, monospace)); }
/// <summary> /// Renders the <see cref="Duality.Resources.Font"/> using the specified system font. /// This method assumes that the system font's size and style match the one specified in /// the specified Duality font. /// </summary> private RenderedFontData RenderGlyphs(SysDrawFont internalFont, FontCharSet charSet, bool antialiazing, bool monospace) { DualityFont.GlyphData[] glyphs = new DualityFont.GlyphData[charSet.Chars.Length]; for (int i = 0; i < glyphs.Length; i++) { glyphs[i].Glyph = charSet.Chars[i]; } int bodyAscent = 0; int baseLine = 0; int descent = 0; int ascent = 0; TextRenderingHint textRenderingHint; if (antialiazing) textRenderingHint = TextRenderingHint.AntiAliasGridFit; else textRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(glyphs.Length)); PixelData pixelLayer = new PixelData( MathF.RoundToInt(cols * internalFont.Size * 1.2f), MathF.RoundToInt(rows * internalFont.Height * 1.2f), ColorRgba.TransparentBlack); PixelData glyphTemp; PixelData glyphTempTypo; Bitmap bm; Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[glyphs.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { Brush fntBrush = new SolidBrush(Color.Black); StringFormat formatDef = StringFormat.GenericDefault; formatDef.LineAlignment = StringAlignment.Near; formatDef.FormatFlags = 0; StringFormat formatTypo = StringFormat.GenericTypographic; formatTypo.LineAlignment = StringAlignment.Near; int x = 1; int y = 1; for (int i = 0; i < glyphs.Length; ++i) { string str = glyphs[i].Glyph.ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, internalFont, pixelLayer.Width, formatDef); // Rasterize a single glyph for rendering bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphTemp = new PixelData(); glyphTemp.FromBitmap(bm); // Rasterize a single glyph in typographic mode for metric analysis if (!isSpace) { Point2 glyphTempOpaqueTopLeft; Point2 glyphTempOpaqueSize; glyphTemp.GetOpaqueBoundaries(out glyphTempOpaqueTopLeft, out glyphTempOpaqueSize); glyphTemp.SubImage(glyphTempOpaqueTopLeft.X, 0, glyphTempOpaqueSize.X, glyphTemp.Height); if (charSet.CharBodyAscentRef.Contains(glyphs[i].Glyph)) bodyAscent += glyphTempOpaqueSize.Y; if (charSet.CharBaseLineRef.Contains(glyphs[i].Glyph)) baseLine += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y; if (charSet.CharDescentRef.Contains(glyphs[i].Glyph)) descent += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y; bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new PixelData(); glyphTempTypo.FromBitmap(bm); glyphTempTypo.Crop(true, false); } else { glyphTempTypo = glyphTemp; } // Update xy values if it doesn't fit anymore if (x + glyphTemp.Width + 2 > pixelLayer.Width) { x = 1; y += internalFont.Height + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data glyphs[i].Width = glyphTemp.Width; glyphs[i].Height = glyphTemp.Height; glyphs[i].OffsetX = glyphTemp.Width - glyphTempTypo.Width; glyphs[i].OffsetY = 0; // ttf fonts are rendered on blocks that are the whole size of the height - so no need for offset if (isSpace) { glyphs[i].Width /= 2; glyphs[i].OffsetX /= 2; } atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphTemp.Width; atlas[i].H = (internalFont.Height + 1); // Draw it onto the font surface glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphTemp.Width + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.125f), 2, 10); } } // White out texture except alpha channel. for (int i = 0; i < pixelLayer.Data.Length; i++) { pixelLayer.Data[i].R = 255; pixelLayer.Data[i].G = 255; pixelLayer.Data[i].B = 255; } // Monospace offset adjustments if (monospace) { int maxGlyphWidth = 0; for (int i = 0; i < glyphs.Length; i++) { maxGlyphWidth = Math.Max(maxGlyphWidth, glyphs[i].Width); } for (int i = 0; i < glyphs.Length; ++i) { glyphs[i].OffsetX -= (int)Math.Round((maxGlyphWidth - glyphs[i].Width) / 2.0f); } } // Determine Font properties { float lineSpacing = internalFont.FontFamily.GetLineSpacing(internalFont.Style); float emHeight = internalFont.FontFamily.GetEmHeight(internalFont.Style); float cellAscent = internalFont.FontFamily.GetCellAscent(internalFont.Style); float cellDescent = internalFont.FontFamily.GetCellDescent(internalFont.Style); ascent = (int)Math.Round(cellAscent * internalFont.Size / emHeight); bodyAscent /= charSet.CharBodyAscentRef.Length; baseLine /= charSet.CharBaseLineRef.Length; descent = (int)Math.Round(((float)descent / charSet.CharDescentRef.Length) - (float)baseLine); } // Aggregate rendered and generated data into our return value FontMetrics metrics = new FontMetrics( size: internalFont.SizeInPoints, height: (int)internalFont.Height, ascent: ascent, bodyAscent: bodyAscent, descent: descent, baseLine: baseLine, monospace: monospace); return new RenderedFontData { Bitmap = pixelLayer, Atlas = atlas, GlyphData = glyphs, Metrics = metrics }; }
/// <summary> /// Renders the <see cref="Duality.Resources.Font"/> using the specified system font family. /// </summary> private RenderedFontData RenderGlyphs(FontFamily fontFamily, float emSize, FontStyle style, FontCharSet extendedSet, bool antialiasing, bool monospace) { // Determine System.Drawing font style SysDrawFontStyle systemStyle = SysDrawFontStyle.Regular; if (style.HasFlag(FontStyle.Bold)) systemStyle |= SysDrawFontStyle.Bold; if (style.HasFlag(FontStyle.Italic)) systemStyle |= SysDrawFontStyle.Italic; // Create a System.Drawing font SysDrawFont internalFont = null; if (fontFamily != null) { try { internalFont = new SysDrawFont(fontFamily, emSize, systemStyle); } catch (Exception e) { Log.Editor.WriteError( "Failed to create System Font '{1} {2}, {3}' for rendering Duality Font glyphs: {0}", Log.Exception(e), fontFamily.Name, emSize, style); } } // If creating the font failed, fall back to a default one if (internalFont == null) internalFont = new SysDrawFont(FontFamily.GenericMonospace, emSize, systemStyle); // Render the font's glyphs using (internalFont) { return this.RenderGlyphs( internalFont, FontCharSet.Default.MergedWith(extendedSet), antialiasing, monospace); } }
/// <summary> /// Renders the <see cref="Duality.Resources.Font"/> based on its embedded TrueType representation. /// <param name="extendedSet">Extended set of characters for renderning.</param> /// </summary> private RenderedFontData RenderGlyphs(byte[] trueTypeFontData, float emSize, FontStyle style, FontCharSet extendedSet, bool antialiasing, bool monospace) { if (this.fontManagers == null) this.fontManagers = new Dictionary<int, PrivateFontCollection>(); // Allocate one PrivateFontCollection for each embedded TrueType Font // This is an unfortunate requirement to keep track of which Font is which, // since a byte[] doesn't give it away, and a Font collection won't tell us // which one we just added. PrivateFontCollection manager; int fontId = trueTypeFontData.GetHashCode(); if (!this.fontManagers.TryGetValue(fontId, out manager)) { manager = new PrivateFontCollection(); this.fontManagers.Add(fontId, manager); } // Load custom font family using System.Drawing if (manager.Families.Length == 0) { IntPtr fontBuffer = Marshal.AllocCoTaskMem(trueTypeFontData.Length); Marshal.Copy(trueTypeFontData, 0, fontBuffer, trueTypeFontData.Length); manager.AddMemoryFont(fontBuffer, trueTypeFontData.Length); } // Render the font's glyphs return this.RenderGlyphs( manager.Families.FirstOrDefault(), emSize, style, extendedSet, antialiasing, monospace); // Yes, we have a minor memory leak here - both the Font buffer and the private // Font collection. Unfortunately though, GDI+ won't let us dispose them // properly due to aggressive Font caching, see here: // // http://stackoverflow.com/questions/25583394/privatefontcollection-addmemoryfont-producing-random-errors-on-windows-server-20 // // "Standard GDI+ lossage, disposing a Font does not actually destroy it. // It gets put back into a cache, with the assumption that it will be used again. // An important perf optimization, creating fonts is pretty expensive. That ends // poorly for private fonts when you destroy their home, the font will use // released memory. Producing bewildering results, including hard crashes. You'll // need to keep the collection around, as well as the IntPtr." // – Hans Passant Aug 30 '14 at 16:13 }