public void Deserialize(string input, out int pageCount, out char[] charSet) { CharSetMapping = new Dictionary<char, FontGlyph>(); var charSetList = new List<char>(); pageCount = 0; XmlTextReader r = new XmlTextReader(new StringReader(input)); while (r.Read()) { if (r.NodeType == XmlNodeType.Element && r.Name == "FontData") { pageCount = int.Parse(r.GetAttribute("Pages")); int glyphCount = int.Parse(r.GetAttribute("CharSetLen")); while (r.Read() && r.NodeType == XmlNodeType.Element && r.Name == "Glyph") { if (CharSetMapping.Count < glyphCount) { char c = r.GetAttribute("char")[0]; var vals = r.GetAttribute("rect").Split(' '); var glyph = new FontGlyph(int.Parse(r.GetAttribute("page")), new Rectangle(int.Parse(vals[0]), int.Parse(vals[1]), int.Parse(vals[2]), int.Parse(vals[3])), int.Parse(r.GetAttribute("yoffset")), c); CharSetMapping.Add(c, glyph); charSetList.Add(c); } } } } charSet = charSetList.ToArray(); }
public static void CreateBitmapPerGlyph(FontGlyph[] sourceGlyphs, HelperBitmap[] sourceBitmaps, out FontGlyph[] destGlyphs, out HelperBitmap[] destBitmaps) { destBitmaps = new HelperBitmap[sourceGlyphs.Length]; destGlyphs = new FontGlyph[sourceGlyphs.Length]; for (int i = 0; i < sourceGlyphs.Length; i++) { var sg = sourceGlyphs[i]; destGlyphs[i] = new FontGlyph(i, new Rectangle(0, 0, sg.rect.Width, sg.rect.Height), sg.yOffset, sg.character); destBitmaps[i] = new HelperBitmap(new Bitmap(sg.rect.Width, sg.rect.Height, PixelFormat.Format32bppArgb)); HelperBitmap.Blit(sourceBitmaps[sg.page].bitmapData, destBitmaps[i].bitmapData, sg.rect, 0, 0); } }
private static int Kerning(FontGlyph g1, FontGlyph g2, XLimits[] lim1, XLimits[] lim2, FontKerningConfiguration config) { int yOffset1 = g1.yOffset; int yOffset2 = g2.yOffset; int startY = Math.Max(yOffset1, yOffset2); int endY = Math.Min(g1.rect.Height + yOffset1, g2.rect.Height + yOffset2); int w1 = g1.rect.Width; int worstCase = w1; for (int j = startY; j < endY; j++) worstCase = Math.Min(worstCase, w1 - lim1[j - yOffset1].Max + lim2[j - yOffset2].Min); worstCase = Math.Min(worstCase, g1.rect.Width); worstCase = Math.Min(worstCase, g2.rect.Width); //modify by character kerning rules CharacterKerningRule kerningRule = config.GetOverridingCharacterKerningRuleForPair("" + g1.character + g2.character); if (kerningRule == CharacterKerningRule.Zero) { return 0; } else if (kerningRule == CharacterKerningRule.NotMoreThanHalf) { return (int)Math.Min(Math.Min(g1.rect.Width, g2.rect.Width) * 0.5f, worstCase); } return worstCase; }
public static Dictionary<String, int> CalculateKerning(char[] charSet, FontGlyph[] glyphs, List<HelperBitmap> bitmapPages, FontKerningConfiguration config) { var kerningPairs = new Dictionary<String, int>(); //we start by computing the index of the first and last non-empty pixel in each row of each glyph XLimits[][] limits = new XLimits[charSet.Length][]; int maxHeight = 0; for (int n = 0; n < charSet.Length; n++) { var rect = glyphs[n].rect; var page = bitmapPages[glyphs[n].page]; limits[n] = new XLimits[rect.Height]; maxHeight = Math.Max(rect.Height, maxHeight); int yStart = rect.Y; int yEnd = rect.Y + rect.Height; int xStart = rect.X; int xEnd = rect.X + rect.Width; for (int j = yStart; j < yEnd; j++) { int last = xStart; bool yetToFindFirst = true; for (int i = xStart; i < xEnd; i++) { if (!HelperBitmap.EmptyAlphaPixel(page.bitmapData, i, j, config.alphaEmptyPixelTolerance)) { if (yetToFindFirst) { limits[n][j - yStart].Min = i - xStart; yetToFindFirst = false; } last = i; } } limits[n][j - yStart].Max = last - xStart; if (yetToFindFirst) limits[n][j - yStart].Min = xEnd - 1; } } //we now bring up each row to the max (or min) of it's two adjacent rows, this is to stop glyphs sliding together too closely var tmp = new XLimits[maxHeight]; for (int n = 0; n < charSet.Length; n++) { //clear tmp for (int j = 0; j < limits[n].Length; j++) tmp[j] = limits[n][j]; for (int j = 0; j < limits[n].Length; j++) { if (j != 0) { tmp[j].Min = Math.Min(limits[n][j - 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j - 1].Max, tmp[j].Max); } if (j != limits[n].Length - 1) { tmp[j].Min = Math.Min(limits[n][j + 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j + 1].Max, tmp[j].Max); } } for (int j = 0; j < limits[n].Length; j++) limits[n][j] = tmp[j]; } for (int i = 0; i < charSet.Length; i++) for (int j = 0; j < charSet.Length; j++) kerningPairs.Add("" + charSet[i] + charSet[j], 1 - Kerning(glyphs[i], glyphs[j], limits[i], limits[j], config)); return kerningPairs; }
//The initial bitmap is simply a long thin strip of all glyphs in a row private Bitmap CreateInitialBitmap(Font font, SizeF maxSize, int initialMargin, out FontGlyph[] glyphs, TextGenerationRenderHint renderHint) { glyphs = new FontGlyph[charSet.Length]; int spacing = (int)Math.Ceiling(maxSize.Width) + 2 * initialMargin; Bitmap bmp = new Bitmap(spacing * charSet.Length, (int)Math.Ceiling(maxSize.Height) + 2 * initialMargin, PixelFormat.Format24bppRgb); var graph = System.Drawing.Graphics.FromImage(bmp); switch (renderHint) { case TextGenerationRenderHint.SizeDependent: graph.TextRenderingHint = font.Size <= 12.0f ? TextRenderingHint.ClearTypeGridFit : TextRenderingHint.AntiAlias; break; case TextGenerationRenderHint.AntiAlias: graph.TextRenderingHint = TextRenderingHint.AntiAlias; break; case TextGenerationRenderHint.AntiAliasGridFit: graph.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; break; case TextGenerationRenderHint.ClearTypeGridFit: graph.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; break; case TextGenerationRenderHint.SystemDefault: graph.TextRenderingHint = TextRenderingHint.SystemDefault; break; } int xOffset = initialMargin; for (int i = 0; i < charSet.Length; i++) { graph.DrawString("" + charSet[i], font, Brushes.White, xOffset, initialMargin); var charSize = graph.MeasureString("" + charSet[i], font); glyphs[i] = new FontGlyph(0, new Rectangle(xOffset - initialMargin, 0, (int)charSize.Width + initialMargin * 2, (int)charSize.Height + initialMargin * 2), 0, charSet[i]); xOffset += (int)charSize.Width + initialMargin * 2; } graph.Flush(); graph.Dispose(); return bmp; }
private static void ScaleSheetsAndGlyphs(List<HelperBitmap> pages, FontGlyph[] glyphs, float scale) { foreach (var page in pages) page.DownScale32((int)(page.bitmap.Width * scale), (int)(page.bitmap.Height * scale)); foreach (var glyph in glyphs) { glyph.rect = new Rectangle((int)(glyph.rect.X * scale), (int)(glyph.rect.Y * scale), (int)(glyph.rect.Width * scale), (int)(glyph.rect.Height * scale)); glyph.yOffset = (int)(glyph.yOffset * scale); } }
private static void RetargetGlyphRectangleOutwards(BitmapData bitmapData, FontGlyph glyph, bool setYOffset, byte alphaTolerance) { int startX, endX; int startY, endY; var rect = glyph.rect; EmptyDel emptyPix; if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) emptyPix = delegate(BitmapData data, int x, int y) { return HelperBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; else emptyPix = delegate(BitmapData data, int x, int y) { return HelperBitmap.EmptyPixel(data, x, y); }; unsafe { for (startX = rect.X; startX >= 0; startX--) { bool foundPix = false; for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, startX, j)) { foundPix = true; break; } } if (!foundPix) { startX++; break; } } for (endX = rect.X + rect.Width; endX < bitmapData.Width; endX++) { bool foundPix = false; for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, endX, j)) { foundPix = true; break; } } if (!foundPix) { endX--; break; } } for (startY = rect.Y; startY >= 0; startY--) { bool foundPix = false; for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, startY)) { foundPix = true; break; } } if (!foundPix) { startY++; break; } } for (endY = rect.Y + rect.Height; endY < bitmapData.Height; endY++) { bool foundPix = false; for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, endY)) { foundPix = true; break; } } if (!foundPix) { endY--; break; } } } glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) glyph.yOffset = glyph.rect.Y; }
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, FontGlyph glyph, bool setYOffset, byte alphaTolerance) { int startX, endX; int startY, endY; var rect = glyph.rect; EmptyDel emptyPix; if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) emptyPix = delegate(BitmapData data, int x, int y) { return HelperBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; else emptyPix = delegate(BitmapData data, int x, int y) { return HelperBitmap.EmptyPixel(data, x, y); }; unsafe { for (startX = rect.X; startX < bitmapData.Width; startX++) for (int j = rect.Y; j < rect.Y + rect.Height; j++) if (!emptyPix(bitmapData, startX, j)) goto Done1; Done1: for (endX = rect.X + rect.Width; endX >= 0; endX--) for (int j = rect.Y; j < rect.Y + rect.Height; j++) if (!emptyPix(bitmapData, endX, j)) goto Done2; Done2: for (startY = rect.Y; startY < bitmapData.Height; startY++) for (int i = startX; i < endX; i++) if (!emptyPix(bitmapData, i, startY)) goto Done3; Done3: for (endY = rect.Y + rect.Height; endY >= 0; endY--) for (int i = startX; i < endX; i++) if (!emptyPix(bitmapData, i, endY)) goto Done4; Done4: ; } if (endY < startY) startY = endY = rect.Y; if (endX < startX) startX = endX = rect.X; glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) glyph.yOffset = glyph.rect.Y; }
private static void RetargetAllGlyphs(List<HelperBitmap> pages, FontGlyph[] glyphs, byte alphaTolerance) { foreach (var glyph in glyphs) RetargetGlyphRectangleOutwards(pages[glyph.page].bitmapData, glyph, false, alphaTolerance); }
private static List<HelperBitmap> GenerateBitmapSheetsAndRepack(FontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out FontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) { var pages = new List<HelperBitmap>(); destGlyphs = new FontGlyph[sourceGlyphs.Length]; HelperBitmap currentPage = null; int maxY = 0; foreach (var glph in sourceGlyphs) maxY = Math.Max(glph.rect.Height, maxY); int finalPageIndex = 0; int finalPageRequiredWidth = 0; int finalPageRequiredHeight = 0; for (int k = 0; k < 2; k++) { bool pre = k == 0; //first iteration is simply to determine the required size of the final page, so that we can crop it in advance int xPos = 0; int yPos = 0; int maxYInRow = 0; int totalTries = 0; for (int i = 0; i < sourceGlyphs.Length; i++) { if (!pre && currentPage == null) { if (finalPageIndex == pages.Count) { int width = Math.Min(destSheetWidth, usePowerOfTwo ? PowerOfTwo(finalPageRequiredWidth) : finalPageRequiredWidth); int height = Math.Min(destSheetHeight, usePowerOfTwo ? PowerOfTwo(finalPageRequiredHeight) : finalPageRequiredHeight); currentPage = new HelperBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new HelperBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } pages.Add(currentPage); } totalTries++; if (totalTries > 10 * sourceGlyphs.Length) throw new Exception("Failed to fit font into texture pages"); var rect = sourceGlyphs[i].rect; if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight) { if (!pre) { //add to page if (sourceBitmaps[sourceGlyphs[i].page].PixelFormat == PixelFormat.Format32bppArgb) HelperBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); else HelperBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); destGlyphs[i] = new FontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].yOffset, sourceGlyphs[i].character); } else { finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); } xPos += rect.Width + 2 * destMargin; maxYInRow = Math.Max(maxYInRow, rect.Height); continue; } if (xPos + rect.Width + 2 * destMargin > destSheetWidth) { i--; yPos += maxYInRow + 2 * destMargin; xPos = 0; if (yPos + maxY + 2 * destMargin > destSheetHeight) { yPos = 0; if (!pre) { currentPage = null; } else { finalPageRequiredWidth = 0; finalPageRequiredHeight = 0; finalPageIndex++; } } continue; } } } return pages; }
private static Dictionary<char, FontGlyph> CreateCharGlyphMapping(FontGlyph[] glyphs) { var dict = new Dictionary<char, FontGlyph>(); for (int i = 0; i < glyphs.Length; i++) dict.Add(glyphs[i].character, glyphs[i]); return dict; }