public static BitmapFont FromQFontFile(string filePath, float downSampleFactor, FontKerningConfiguration kerningConfig) { if (kerningConfig == null) kerningConfig = new FontKerningConfiguration(); float fontScale = 1f; BitmapFont qfont = new BitmapFont(); qfont.fontData = Builder.LoadQFontDataFromFile(filePath, downSampleFactor * fontScale, kerningConfig); return qfont; }
public static BitmapFont FromQFontFile(string filePath, FontKerningConfiguration kerningConfig) { return FromQFontFile(filePath, 1.0f, kerningConfig); }
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; }
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 FontData LoadQFontDataFromFile(string filePath, float downSampleFactor, FontKerningConfiguration kerningConfig) { string xmldoc = File.ReadAllText(filePath); var data = new FontData(); int pageCount = 0; char[] charSet; data.Deserialize(xmldoc, out pageCount, out charSet); string namePrefix = filePath.Replace(".bmf", "").Replace(" ", ""); var bitmapPages = new List<HelperBitmap>(); if (pageCount == 1) { bitmapPages.Add(new HelperBitmap(namePrefix + ".png")); } else { for (int i = 0; i < pageCount; i++) bitmapPages.Add(new HelperBitmap(namePrefix + "_" + i + ".png")); } foreach (var glyph in data.CharSetMapping.Values) RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, kerningConfig.alphaEmptyPixelTolerance); var intercept = FirstIntercept(data.CharSetMapping); if (intercept != null) { throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. If you are texturing your font without locking pixel opacity, then consider using a larger glyph margin. This can be done by setting QFontBuilderConfiguration myQfontBuilderConfig.GlyphMargin, and passing it into CreateTextureFontFiles."); } if (downSampleFactor > 1.0f) { foreach (var page in bitmapPages) page.DownScale32((int)(page.bitmap.Width * downSampleFactor), (int)(page.bitmap.Height * downSampleFactor)); foreach (var glyph in data.CharSetMapping.Values) { glyph.rect = new Rectangle((int)(glyph.rect.X * downSampleFactor), (int)(glyph.rect.Y * downSampleFactor), (int)(glyph.rect.Width * downSampleFactor), (int)(glyph.rect.Height * downSampleFactor)); glyph.yOffset = (int)(glyph.yOffset * downSampleFactor); } } else if (downSampleFactor < 1.0f) { // If we were simply to shrink the entire texture, then at some point we will make glyphs overlap, breaking the font. // For this reason it is necessary to copy every glyph to a separate bitmap, and then shrink each bitmap individually. FontGlyph[] shrunkGlyphs; HelperBitmap[] shrunkBitmapsPerGlyph; CreateBitmapPerGlyph(data.CharSetMapping.Values.ToArray(), bitmapPages.ToArray(), out shrunkGlyphs, out shrunkBitmapsPerGlyph); //shrink each bitmap for (int i = 0; i < shrunkGlyphs.Length; i++) { var bmp = shrunkBitmapsPerGlyph[i]; bmp.DownScale32(Math.Max((int)(bmp.bitmap.Width * downSampleFactor), 1), Math.Max((int)(bmp.bitmap.Height * downSampleFactor), 1)); shrunkGlyphs[i].rect = new Rectangle(0, 0, bmp.bitmap.Width, bmp.bitmap.Height); shrunkGlyphs[i].yOffset = (int)(shrunkGlyphs[i].yOffset * downSampleFactor); } var shrunkBitmapData = new BitmapData[shrunkBitmapsPerGlyph.Length]; for (int i = 0; i < shrunkBitmapsPerGlyph.Length; i++) { shrunkBitmapData[i] = shrunkBitmapsPerGlyph[i].bitmapData; } //use roughly the same number of pages as before.. int newWidth = (int)(bitmapPages[0].bitmap.Width * (0.1f + downSampleFactor)); int newHeight = (int)(bitmapPages[0].bitmap.Height * (0.1f + downSampleFactor)); //free old bitmap pages since we are about to chuck them away for (int i = 0; i < pageCount; i++) bitmapPages[i].Free(); FontGlyph[] shrunkRepackedGlyphs; bitmapPages = GenerateBitmapSheetsAndRepack(shrunkGlyphs, shrunkBitmapData, newWidth, newHeight, out shrunkRepackedGlyphs, 4, false); data.CharSetMapping = CreateCharGlyphMapping(shrunkRepackedGlyphs); foreach (var bmp in shrunkBitmapsPerGlyph) bmp.Free(); pageCount = bitmapPages.Count; } data.Pages = new TexturePage[pageCount]; for (int i = 0; i < pageCount; i++) data.Pages[i] = new TexturePage(bitmapPages[i].bitmapData); if (downSampleFactor != 1.0f) { foreach (var glyph in data.CharSetMapping.Values) RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, kerningConfig.alphaEmptyPixelTolerance); intercept = FirstIntercept(data.CharSetMapping); if (intercept != null) { throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This occurred only after resizing your texture font, implying that there is a bug in QFont. "); } } var glyphList = new List<FontGlyph>(); foreach (var c in charSet) glyphList.Add(data.CharSetMapping[c]); data.KerningPairs = KerningCalculator.CalculateKerning(charSet.ToArray(), glyphList.ToArray(), bitmapPages, kerningConfig); data.CalculateMeanWidth(); data.CalculateMaxHeight(); for (int i = 0; i < pageCount; i++) bitmapPages[i].Free(); return data; }