/// <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 }; }
private void GenerateResources() { if (this.mat != null || this.texture != null || this.pixelData != null) this.ReleaseResources(); TextRenderingHint textRenderingHint; if (this.renderMode == RenderMode.MonochromeBitmap) textRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; else textRenderingHint = TextRenderingHint.AntiAliasGridFit; int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(SupportedChars.Length)); PixelData pixelLayer = new PixelData(MathF.RoundToInt(cols * this.internalFont.Size * 1.2f), MathF.RoundToInt(rows * this.internalFont.Height * 1.2f)); PixelData glyphTemp; PixelData glyphTempTypo; Bitmap bm; Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[SupportedChars.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 < SupportedChars.Length; ++i) { string str = SupportedChars[i].ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, this.internalFont, pixelLayer.Width, formatDef); // Rasterize a single glyph for rendering bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphTemp = new PixelData(bm); // Rasterize a single glyph in typographic mode for metric analysis if (!isSpace) { Rectangle glyphTempBounds = glyphTemp.OpaqueBounds(); glyphTemp.SubImage(glyphTempBounds.X, 0, glyphTempBounds.Width, glyphTemp.Height); if (CharBodyAscentRef.Contains(SupportedChars[i])) this.bodyAscent += glyphTempBounds.Height; if (CharBaseLineRef.Contains(SupportedChars[i])) this.baseLine += glyphTempBounds.Bottom; if (CharDescentRef.Contains(SupportedChars[i])) this.descent += glyphTempBounds.Bottom; bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new PixelData(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 += this.internalFont.Height + MathF.Clamp((int)MathF.Ceiling(this.internalFont.Height * 0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, glyphTemp.Width); this.glyphs[i].width = glyphTemp.Width; this.glyphs[i].height = glyphTemp.Height; this.glyphs[i].offsetX = glyphTemp.Width - glyphTempTypo.Width; if (isSpace) { this.glyphs[i].width /= 2; this.glyphs[i].offsetX /= 2; } atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphTemp.Width; atlas[i].H = (this.internalFont.Height + 1); // Draw it onto the font surface glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphTemp.Width + MathF.Clamp((int)MathF.Ceiling(this.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; } // Determine Font properties { float lineSpacing = this.internalFont.FontFamily.GetLineSpacing(this.internalFont.Style); float emHeight = this.internalFont.FontFamily.GetEmHeight(this.internalFont.Style); float cellAscent = this.internalFont.FontFamily.GetCellAscent(this.internalFont.Style); float cellDescent = this.internalFont.FontFamily.GetCellDescent(this.internalFont.Style); float height = this.internalFont.GetHeight(); this.height = this.internalFont.Height; this.ascent = (int)Math.Round(cellAscent * this.internalFont.Size / emHeight); this.bodyAscent /= CharBodyAscentRef.Length; this.baseLine /= CharBaseLineRef.Length; this.descent = (int)Math.Round(((float)this.descent / CharDescentRef.Length) - (float)this.baseLine); //this.descent = (int)Math.Round(cellDescent * height / lineSpacing); //this.baseLine = (int)Math.Round(cellAscent * height / lineSpacing); } // Create internal Pixmap and Texture Resources this.pixelData = new Pixmap(pixelLayer); this.pixelData.Atlas = new List<Rect>(atlas); this.texture = new Texture(this.pixelData, TextureSizeMode.Enlarge, this.IsPixelGridAligned ? TextureMagFilter.Nearest : TextureMagFilter.Linear, this.IsPixelGridAligned ? TextureMinFilter.Nearest : TextureMinFilter.LinearMipmapLinear); // Select DrawTechnique to use ContentRef<DrawTechnique> technique; if (this.renderMode == RenderMode.MonochromeBitmap) technique = DrawTechnique.Mask; else if (this.renderMode == RenderMode.GrayscaleBitmap) technique = DrawTechnique.Alpha; else if (this.renderMode == RenderMode.SmoothBitmap) technique = DrawTechnique.Alpha; else technique = DrawTechnique.SharpAlpha; // Create and configure internal BatchInfo BatchInfo matInfo = new BatchInfo(technique, ColorRgba.White, this.texture); if (technique == DrawTechnique.SharpAlpha) { matInfo.SetUniform("smoothness", this.size * 4.0f); } this.mat = new Material(matInfo); }