public static FontResource Load(Stream stream, IEnumerable<char> targets, int pixelSize) { InitStandardWidths(); int count = targets.Count(); int maxWidth = GetMaxWidth(pixelSize, count); var dict = new FullDictionary<char, CharacterInfo>(CharacterInfo.Default); Bitmap finalBitmap; using (var bitmap = new Bitmap(maxWidth, maxWidth, PixelFormat.Format24bppRgb)) { int currentX = 0, currentY = 0; using (Graphics graphics = Graphics.FromImage(bitmap)) { var typeface = new FontFace(stream); foreach (char c in targets) { BlitCharacter(pixelSize, maxWidth, dict, ref currentX, ref currentY, graphics, typeface, c); } } finalBitmap = ShortenBitmap(bitmap, maxWidth, currentY + yInterval + pixelSize + (pixelSize / 10 > 1 ? pixelSize / 10 : 1)); } var fontResource = new FontResource(); fontResource.FontHeight = pixelSize + yInterval; fontResource.CharInfoDict = dict; fontResource.InitTexture(finalBitmap); finalBitmap.Dispose(); return fontResource; }
public unsafe void Append(TextAnalyzer analyzer, FontFace font, string text) { var layout = new TextLayout(); var format = new TextFormat { Font = font, Size = 32.0f }; analyzer.AppendText(text, format); analyzer.PerformLayout(0, 32, 1000, 1000, layout); var memBlock = new MemoryBlock(text.Length * 6 * PosColorTexture.Layout.Stride); var mem = (PosColorTexture*)memBlock.Data; foreach (var thing in layout.Stuff) { var width = thing.Width; var height = thing.Height; var region = new Vector4(thing.SourceX, thing.SourceY, width, height) / 4096; var origin = new Vector2(thing.DestX, thing.DestY); *mem++ = new PosColorTexture(origin + new Vector2(0, height), new Vector2(region.X, region.Y + region.W), unchecked((int)0xff000000)); *mem++ = new PosColorTexture(origin + new Vector2(width, height), new Vector2(region.X + region.Z, region.Y + region.W), unchecked((int)0xff000000)); *mem++ = new PosColorTexture(origin + new Vector2(width, 0), new Vector2(region.X + region.Z, region.Y), unchecked((int)0xff000000)); *mem++ = new PosColorTexture(origin, new Vector2(region.X, region.Y), unchecked((int)0xff000000)); count++; } vertexBuffer = new DynamicVertexBuffer(memBlock, PosColorTexture.Layout); }
private static unsafe bool RenderGlyph(FontFace typeface, char c, float pixelSize, out Surface surface) { bool result = false; Glyph glyph = typeface.GetGlyph(c, pixelSize); if (glyph != null && glyph.RenderWidth > 0 && glyph.RenderHeight > 0) { surface = new Surface { Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight), Width = glyph.RenderWidth, Height = glyph.RenderHeight, Pitch = glyph.RenderWidth }; var stuff = (byte*)surface.Bits; for (int i = 0; i < surface.Width * surface.Height; i++) *stuff++ = 0; glyph.RenderTo(surface); result = true; } else { surface = new Surface(); } return result; }
static void Main(string[] args) { using (FileStream file = File.OpenRead("../../../Fonts/OpenSans-Regular.ttf")) { var typeface = new FontFace(file); long totalLength = 0; int average = 0; for (int c = 0; c <= char.MaxValue; c++) { Console.WriteLine("Dump {0}: {1}", c, (char)c); string comparisonFile = Path.Combine(ComparisonPath, (int)c + ".png"); Surface surface; if (RenderGlyph(typeface, (char)c, 32, out surface)) { SaveSurface(surface, comparisonFile); totalLength += surface.Width; surface.Dispose(); } } Console.WriteLine("Total width: {0}", totalLength); } Console.Read(); }
private static void BlitCharacter(int pixelSize, int maxWidth, FullDictionary<char, CharacterInfo> dict, ref int currentX, ref int currentY, Graphics g, FontFace typeface, char c) { if (c == ' ' || c == '\t') { int width = (c == ' ') ? pixelSize / 2 : pixelSize * 4; if (currentX + xInterval + width >= maxWidth) { currentX = 0; currentY += yInterval + pixelSize; if (currentY + yInterval + pixelSize >= maxWidth) { throw new Exception("Texture Size not big enough for required characters."); } } Bitmap glyphBitmap = new Bitmap(width + xInterval, pixelSize + yInterval); //float yoffset = pixelSize * 3 / 4 - glyph.HorizontalMetrics.Bearing.Y; g.DrawImage(glyphBitmap, currentX + xInterval, currentY + yInterval); CharacterInfo info = new CharacterInfo(currentX, currentY, width + xInterval, pixelSize + yInterval); dict.Add(c, info); glyphBitmap.Dispose(); currentX += width; } else { Surface surface; Glyph glyph; if (RenderGlyph(typeface, c, pixelSize, out surface, out glyph)) { if (currentX + xInterval + surface.Width >= maxWidth) { currentX = 0; currentY += yInterval + pixelSize; if (currentY + yInterval + pixelSize >= maxWidth) { throw new Exception("Texture Size not big enough for required characters."); } } Bitmap glyphBitmap = GetGlyphBitmap(surface); const int a = 5; const int b = 8; //float yoffset = pixelSize * a / b - glyph.HorizontalMetrics.Bearing.Y; float skyHeight = yInterval + pixelSize * a / b - glyph.HorizontalMetrics.Bearing.Y; if (skyHeight < 0) { skyHeight = 0; } if (skyHeight < 0) { skyHeight = 0; } else if (skyHeight + glyphBitmap.Height > yInterval + pixelSize) { skyHeight -= glyphBitmap.Height - (yInterval + pixelSize); } g.DrawImage(glyphBitmap, currentX + xInterval, currentY + skyHeight, glyphBitmap.Width, glyphBitmap.Height); #if DEBUG g.DrawRectangle(greenPen, currentX, currentY, glyphBitmap.Width + xInterval, yInterval + pixelSize - 1); g.DrawRectangle(redPen, currentX + xInterval, currentY + skyHeight, glyphBitmap.Width, glyphBitmap.Height); g.DrawLine(bluePen, currentX, currentY + yInterval + pixelSize * a / b, currentX + glyphBitmap.Width, currentY + yInterval + pixelSize * a / b); #endif CharacterInfo info = new CharacterInfo(currentX, currentY, glyphBitmap.Width + xInterval, yInterval + pixelSize - 1); dict.Add(c, info); glyphBitmap.Dispose(); currentX += xInterval + surface.Width; } surface.Dispose(); } }
public void AppendText(char *text, int count, TextFormat format) { // look up the cache entry for the given font and size CachedFace cachedFace; var font = format.Font; var size = FontFace.ComputePixelSize(format.Size, Dpi); var key = new CacheKey(font.Id, size); if (!cache.TryGetValue(key, out cachedFace)) { cache.Add(key, cachedFace = new CachedFace(font, size)); } // process each character in the string var nextBreak = BreakCategory.None; var previous = new CodePoint(); char *end = text + count; while (text != end) { // handle surrogate pairs properly CodePoint codePoint; char c = *text++; if (char.IsSurrogate(c) && text != end) { codePoint = new CodePoint(c, *text++); } else { codePoint = c; } // ignore linefeeds directly after a carriage return if (c == '\n' && (char)previous == '\r') { continue; } // get the glyph data CachedGlyph glyph; if (!cachedFace.Glyphs.TryGetValue(codePoint, out glyph) && !char.IsControl(c)) { var data = font.GetGlyph(codePoint, size); var width = data.RenderWidth; var height = data.RenderHeight; if (width > atlas.Width || height > atlas.Height) { throw new InvalidOperationException("Glyph is larger than the size of the provided atlas."); } var rect = new Rect(); if (width > 0 && height > 0) { // render the glyph var memSize = width * height; var mem = memoryBuffer; if (mem == null) { memoryBuffer = mem = new MemoryBuffer(memSize); } mem.Clear(memSize); data.RenderTo(new Surface { Bits = mem.Pointer, Width = width, Height = height, Pitch = width }); // save the rasterized glyph in the user's atlas rect = packer.Insert(width, height); if (rect.Height == 0) { // didn't fit in the atlas... start a new sheet currentPage++; packer.Clear(atlas.Width, atlas.Height); rect = packer.Insert(width, height); if (rect.Height == 0) { throw new InvalidOperationException("Failed to insert glyph into fresh page."); } } atlas.Insert(currentPage, rect.X, rect.Y, rect.Width, rect.Height, mem.Pointer); } glyph = new CachedGlyph(rect, data.HorizontalMetrics.Bearing, data.HorizontalMetrics.Advance); cachedFace.Glyphs.Add(codePoint, glyph); } // check for a kerning offset var kerning = font.GetKerning(previous, codePoint, size); previous = codePoint; // figure out whether this character can serve as a line break point // TODO: more robust character class handling var breakCategory = BreakCategory.None; if (char.IsWhiteSpace(c)) { if (c == '\r' || c == '\n') { breakCategory = BreakCategory.Mandatory; } else { breakCategory = BreakCategory.Opportunity; } } // the previous character might make us think that this one should be a break opportunity if (nextBreak > breakCategory) { breakCategory = nextBreak; } if (c == '-') { nextBreak = BreakCategory.Opportunity; } // alright, we have all the right glyph data cached and loaded // append relevant info to our buffer; we'll do the actual layout later buffer.Add(new BufferEntry { GlyphData = glyph, Kerning = kerning, Break = breakCategory }); } }
public CachedFace(FontFace font, float size) { Metrics = font.GetFaceMetrics(size); Glyphs = new Dictionary <CodePoint, CachedGlyph>(); }