public BitmapFont(Font font, FontBuilderConfiguration config) { if (config == null) config = new FontBuilderConfiguration(); fontData = BuildFont(font, config, null); }
public BitmapFont(string fileName, float size, FontStyle style, FontBuilderConfiguration config) { PrivateFontCollection pfc = new PrivateFontCollection(); pfc.AddFontFile(fileName); var fontFamily = pfc.Families[0]; if (!fontFamily.IsStyleAvailable(style)) throw new ArgumentException("Font file: " + fileName + " does not support style: " + style); if (config == null) config = new FontBuilderConfiguration(); float fontScale = 1f; using (var font = new Font(fontFamily, size * fontScale * config.SuperSampleLevels, style)) { fontData = BuildFont(font, config, null); } }
internal BitmapFont(FontData fontData) { this.fontData = fontData; }
public static void CreateTextureFontFiles(string fileName, float size, FontStyle style, FontBuilderConfiguration config, string newFontName) { PrivateFontCollection pfc = new PrivateFontCollection(); pfc.AddFontFile(fileName); var fontFamily = pfc.Families[0]; if (!fontFamily.IsStyleAvailable(style)) throw new ArgumentException("Font file: " + fileName + " does not support style: " + style); FontData fontData = null; if (config == null) config = new FontBuilderConfiguration(); using (var font = new Font(fontFamily, size * config.SuperSampleLevels, style)) { fontData = BuildFont(font, config, newFontName); } Builder.SaveQFontDataToFile(fontData, newFontName); }
private float MeasureTextNodeLength(TextNode node, FontData fontData, FontRenderOptions options) { bool monospaced = fontData.IsMonospacingActive(options); float monospaceWidth = fontData.GetMonoSpaceWidth(options); if (node.Type == TextNodeType.Space) { if (monospaced) return monospaceWidth; return (float)Math.Ceiling(fontData.meanGlyphWidth * options.WordSpacing); } float length = 0f; if (node.Type == TextNodeType.Word) { for (int i = 0; i < node.Text.Length; i++) { char c = node.Text[i]; if (fontData.CharSetMapping.ContainsKey(c)) { if (monospaced) length += monospaceWidth; else length += (float)Math.Ceiling(fontData.CharSetMapping[c].rect.Width + fontData.meanGlyphWidth * options.CharacterSpacing + fontData.GetKerningPairCorrection(i, node.Text, node)); } } } return length; }
public void MeasureNodes(FontData fontData, FontRenderOptions options) { foreach (TextNode node in this) { if (node.Length == 0f) node.Length = MeasureTextNodeLength(node, fontData, options); } }
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; }
public FontData BuildFontData(string saveName) { if (config.ForcePowerOfTwo && config.SuperSampleLevels != PowerOfTwo(config.SuperSampleLevels)) { throw new ArgumentOutOfRangeException("SuperSampleLevels must be a power of two when using ForcePowerOfTwo."); } if (config.SuperSampleLevels <= 0 || config.SuperSampleLevels > 8) { throw new ArgumentOutOfRangeException("SuperSampleLevels = [" + config.SuperSampleLevels + "] is an unsupported value. Please use values in the range [1,8]"); } int margin = 2; //margin in initial bitmap (don't bother to make configurable - likely to cause confusion int pageWidth = config.PageWidth * config.SuperSampleLevels; //texture page width int pageHeight = config.PageHeight * config.SuperSampleLevels; //texture page height bool usePowerOfTwo = config.ForcePowerOfTwo; int glyphMargin = config.GlyphMargin * config.SuperSampleLevels; FontGlyph[] initialGlyphs; var sizes = GetGlyphSizes(font); var maxSize = GetMaxGlyphSize(sizes); var initialBmp = CreateInitialBitmap(font, maxSize, margin, out initialGlyphs, config.TextGenerationRenderHint); var initialBitmapData = initialBmp.LockBits(new Rectangle(0, 0, initialBmp.Width, initialBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int minYOffset = int.MaxValue; foreach (var glyph in initialGlyphs) { RetargetGlyphRectangleInwards(initialBitmapData, glyph, true, config.KerningConfig.alphaEmptyPixelTolerance); minYOffset = Math.Min(minYOffset, glyph.yOffset); } minYOffset--; //give one pixel of breathing room? foreach (var glyph in initialGlyphs) glyph.yOffset -= minYOffset; FontGlyph[] glyphs; var bitmapPages = GenerateBitmapSheetsAndRepack(initialGlyphs, new BitmapData[1] { initialBitmapData }, pageWidth, pageHeight, out glyphs, glyphMargin, usePowerOfTwo); initialBmp.UnlockBits(initialBitmapData); initialBmp.Dispose(); if (config.SuperSampleLevels != 1) { ScaleSheetsAndGlyphs(bitmapPages, glyphs, 1.0f / config.SuperSampleLevels); RetargetAllGlyphs(bitmapPages, glyphs, config.KerningConfig.alphaEmptyPixelTolerance); } //create list of texture pages var pages = new List<TexturePage>(); foreach (var page in bitmapPages) pages.Add(new TexturePage(page.bitmapData)); var fontData = new FontData(); fontData.CharSetMapping = CreateCharGlyphMapping(glyphs); fontData.Pages = pages.ToArray(); fontData.CalculateMeanWidth(); fontData.CalculateMaxHeight(); fontData.KerningPairs = KerningCalculator.CalculateKerning(charSet.ToCharArray(), glyphs, bitmapPages, config.KerningConfig); fontData.naturallyMonospaced = IsMonospaced(sizes); if (saveName != null) { if (bitmapPages.Count == 1) bitmapPages[0].bitmap.Save(saveName + ".png", System.Drawing.Imaging.ImageFormat.Png); else { for (int i = 0; i < bitmapPages.Count; i++) bitmapPages[i].bitmap.Save(saveName + "_" + i + ".png", System.Drawing.Imaging.ImageFormat.Png); } } foreach (var page in bitmapPages) page.Free(); //validate glyphs var intercept = FirstIntercept(fontData.CharSetMapping); if (intercept != null) throw new Exception("Failed to create glyph set. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This is could be due to an error in the font, or a bug in Graphics.MeasureString()."); return fontData; }
public static void SaveQFontDataToFile(FontData data, string filePath) { string xmldoc = data.Serialize(); StreamWriter writer = new StreamWriter(filePath + ".bmf"); writer.WriteLine(xmldoc); writer.Close(); }