public void Deserialize(List <String> input, out int pageCount, out char[] charSet) { CharSetMapping = new Dictionary <char, QFontGlyph>(); var charSetList = new List <char>(); try { pageCount = int.Parse(input[0]); int glyphCount = int.Parse(input[1]); for (int i = 0; i < glyphCount; i++) { var vals = input[2 + i].Split(' '); var glyph = new QFontGlyph(int.Parse(vals[1]), new Rectangle(int.Parse(vals[2]), int.Parse(vals[3]), int.Parse(vals[4]), int.Parse(vals[5])), int.Parse(vals[6])); CharSetMapping.Add(vals[0][0], glyph); charSetList.Add(vals[0][0]); } } catch (Exception e) { throw new Exception("Failed to parse qfont file. Invalid format.", e); } charSet = charSetList.ToArray(); }
public void Deserialize(List<String> input, out int pageCount, out char[] charSet) { CharSetMapping = new Dictionary<char, QFontGlyph>(); var charSetList = new List<char>(); try { pageCount = int.Parse(input[0]); int glyphCount = int.Parse(input[1]); for (int i = 0; i < glyphCount; i++) { var vals = input[2 + i].Split(' '); var glyph = new QFontGlyph(int.Parse(vals[1]), new Rectangle(int.Parse(vals[2]), int.Parse(vals[3]), int.Parse(vals[4]), int.Parse(vals[5])), int.Parse(vals[6])); CharSetMapping.Add(vals[0][0], glyph); charSetList.Add(vals[0][0]); } } catch (Exception e) { throw new Exception("Failed to parse qfont file. Invalid format.",e); } charSet = charSetList.ToArray(); }
private float MeasureNextlineLength(string text) { float xOffset = 0; for (int i = 0; i < text.Length; i++) { char c = text[i]; if (c == '\r' || c == '\n') { break; } if (IsMonospacingActive) { xOffset += MonoSpaceWidth; } else { //space if (c == ' ') { xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * Options.WordSpacing); } //normal character else if (fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); } } } return(xOffset); }
/* * private SizeF GetMaxGlyphSize(Font font) * { * Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb); * Graphics graph = Graphics.FromImage(bmp); * * SizeF maxSize = new SizeF(0f, 0f); * for (int i = 0; i < charSet.Length; i++) * { * var charSize = graph.MeasureString("" + charSet[i], font); * * if (charSize.Width > maxSize.Width) * maxSize.Width = charSize.Width; * * if (charSize.Height > maxSize.Height) * maxSize.Height = charSize.Height; * } * * graph.Dispose(); * bmp.Dispose(); * * return maxSize; * }*/ private Bitmap CreateInitialBitmap(Font font, SizeF maxSize, int initialMargin, out QFontGlyph[] glyphs) { glyphs = new QFontGlyph[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); Graphics graph = Graphics.FromImage(bmp); graph.TextRenderingHint = TextRenderingHint.AntiAlias; 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 QFontGlyph(0, new Rectangle(xOffset - initialMargin, 0, (int)charSize.Width + initialMargin * 2, (int)charSize.Height + initialMargin * 2), 0); xOffset += (int)charSize.Width + initialMargin * 2; } graph.Flush(); graph.Dispose(); return(bmp); }
private static int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2) { 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; //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 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); return(worstCase); }
private void RenderWord(float x, float y, TextNode node, ref Rectangle clippingRectangle) { if (node.Type != TextNodeType.Word) { return; } int charGaps = node.Text.Length - 1; bool isCrumbleWord = CrumbledWord(node); if (isCrumbleWord) { charGaps++; } int pixelsPerGap = 0; int leftOverPixels = 0; if (charGaps != 0) { pixelsPerGap = (int)node.LengthTweak / charGaps; leftOverPixels = (int)node.LengthTweak - pixelsPerGap * charGaps; } for (int i = 0; i < node.Text.Length; i++) { char c = node.Text[i]; if (_font.FontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = _font.FontData.CharSetMapping[c]; RenderGlyph(x, y, c, _font, CurrentVertexRepr, clippingRectangle); if (IsMonospacingActive) { x += MonoSpaceWidth; } else { x += (int) Math.Ceiling(glyph.rect.Width + _font.FontData.meanGlyphWidth * this.Options.CharacterSpacing + _font.FontData.GetKerningPairCorrection(i, node.Text, node)); } x += pixelsPerGap; if (leftOverPixels > 0) { x += 1.0f; leftOverPixels--; } else if (leftOverPixels < 0) { x -= 1.0f; leftOverPixels++; } } } }
private static Dictionary<char, QFontGlyph> CreateCharGlyphMapping(QFontGlyph[] glyphs) { var dict = new Dictionary<char, QFontGlyph>(); for (int i = 0; i < glyphs.Length; i++) dict.Add(glyphs[i].character, glyphs[i]); return dict; }
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph 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 QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; else emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.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 void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph, QFont shadowFont, ref Rectangle clippingRectangle) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (shadowFont != null && this.Options.DropShadowActive) { float xOffset = (_font.FontData.meanGlyphWidth * this.Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f); float yOffset = (_font.FontData.meanGlyphWidth * this.Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset); this.RenderGlyph(x + xOffset, y + yOffset, c, shadowFont, this.ShadowVertexRepr, clippingRectangle); } }
public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps){ destBitmaps = new QBitmap[sourceGlyphs.Length]; destGlyphs = new QFontGlyph[sourceGlyphs.Length]; for(int i = 0; i < sourceGlyphs.Length; i++){ var sg = sourceGlyphs[i]; destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character); destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb)); QBitmap.Blit(sourceBitmaps[sg.page].bitmapData,destBitmaps[i].bitmapData,sg.rect,0,0); } }
public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps) { destBitmaps = new QBitmap[sourceGlyphs.Length]; destGlyphs = new QFontGlyph[sourceGlyphs.Length]; for(int i = 0; i < sourceGlyphs.Length; i++){ var sg = sourceGlyphs[i]; destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character); destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb)); QBitmap.Blit(sourceBitmaps[sg.page].BitmapData,destBitmaps[i].BitmapData,sg.rect,0,0); } }
private void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph, QFontData shadowFont, ref Rectangle clippingRectangle) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (shadowFont != null && Options.DropShadowActive) { var xOffset = Font.meanGlyphWidth * Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f; var yOffset = Font.meanGlyphWidth * Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset; RenderGlyph(x + xOffset, y + yOffset, c, shadowFont, ShadowVertexRepr, ref clippingRectangle); } }
/// <summary> /// Creates the initial font bitmap. This is simply a long thin strip of all glyphs in a row /// </summary> /// <param name="font">The <see cref="IFont"/> object to build the initial bitmap from</param> /// <param name="maxSize">The maximum glyph size of the font</param> /// <param name="initialMargin">The initial bitmap margin (used for all four sides)</param> /// <param name="glyphs">A collection of <see cref="QFontGlyph"/>s corresponding to the initial bitmap</param> /// <param name="renderHint">The font rendering hint to use</param> /// <returns></returns> private Bitmap CreateInitialBitmap(IFont font, SizeF maxSize, int initialMargin, out QFontGlyph[] glyphs, TextGenerationRenderHint renderHint) { glyphs = new QFontGlyph[_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); Graphics graph = 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; } // enable high quality graphics graph.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graph.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver; int xOffset = initialMargin; for (int i = 0; i < _charSet.Length; i++) { var offset = font.DrawString("" + _charSet[i], graph, Brushes.White, xOffset, initialMargin); var charSize = font.MeasureString("" + _charSet[i], graph); glyphs[i] = new QFontGlyph(0, new Rectangle(xOffset - initialMargin + offset.X, initialMargin + offset.Y, (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 SizeF GetMaxGlyphSize(Font font) * { * Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb); * Graphics graph = Graphics.FromImage(bmp); * * SizeF maxSize = new SizeF(0f, 0f); * for (int i = 0; i < charSet.Length; i++) * { * var charSize = graph.MeasureString("" + charSet[i], font); * * if (charSize.Width > maxSize.Width) * maxSize.Width = charSize.Width; * * if (charSize.Height > maxSize.Height) * maxSize.Height = charSize.Height; * } * * graph.Dispose(); * bmp.Dispose(); * * return maxSize; * }*/ //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 QFontGlyph[] glyphs, TextGenerationRenderHint renderHint) { glyphs = new QFontGlyph[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); Graphics graph = 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 QFontGlyph(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 void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (fontData.dropShadow != null && Options.DropShadowActive) { //make sure fontdata font's options are synced with the actual options if (fontData.dropShadow.Options != Options) { fontData.dropShadow.Options = Options; } fontData.dropShadow.RenderGlyph( x + (fontData.meanGlyphWidth * Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f), y + (fontData.meanGlyphWidth * Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset), c, true); } }
/// <summary> /// Calculate the kerning between two glyphs /// </summary> /// <param name="g1">The first glyph</param> /// <param name="g2">The second glyph</param> /// <param name="lim1">The first glyph limits</param> /// <param name="lim2">The second glyph limits</param> /// <param name="config">The kerning configuration to use</param> /// <param name="font">The glyph's <see cref="IFont"/></param> /// <returns>The x coordinate kerning offset</returns> private static int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2, QFontKerningConfiguration config, IFont font) { // Use kerning information from the font if it exists if (font != null && font.HasKerningInformation) { return(font.GetKerning(g1.Character, g2.Character)); } // Otherwise, calculate our own kerning 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; //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 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); switch (kerningRule) { case CharacterKerningRule.Zero: return(1); case CharacterKerningRule.NotMoreThanHalf: return(1 - (int)Math.Min(Math.Min(g1.Rect.Width, g2.Rect.Width) * 0.5f, worstCase)); } return(1 - worstCase); }
private static int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2, QFontKerningConfiguration 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; //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 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); }
private static int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2, QFontKerningConfiguration 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; //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 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; }
private static List <QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin) { var pages = new List <QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap 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, finalPageRequiredWidth); int height = Math.Min(destSheetHeight, finalPageRequiredHeight); currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(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) { QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } else { QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } destGlyphs[i] = new QFontGlyph(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 void RetargetGlyphRectangleOutwards(BitmapData bitmapData, QFontGlyph 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(QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance)); } } ; else { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.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; } }
public void RetargetGlyphRectangleOutwards (QFontGlyph glyph, bool setYOffset, byte alphaTolerance) { int startX,endX; int startY,endY; var rect = glyph.rect; EmptyDel emptyPix; if (mBitmapData.PixelFormat == PixelFormat.Format32bppArgb) emptyPix = delegate(BitmapData data, int x, int y) { return QBitmapData.EmptyAlphaPixel(data, x, y, alphaTolerance); }; else emptyPix = delegate(BitmapData data, int x, int y) { return QBitmapData.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(mBitmapData, startX, j)) { foundPix = true; break; } } if (!foundPix) { startX++; break; } } for (endX = rect.X + rect.Width; endX < mBitmapData.Width; endX++) { bool foundPix = false; for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(mBitmapData, 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(mBitmapData, i, startY)) { foundPix = true; break; } } if (!foundPix) { startY++; break; } } for (endY = rect.Y + rect.Height; endY < mBitmapData.Height; endY++) { bool foundPix = false; for (int i = startX; i <= endX; i++) { if (!emptyPix(mBitmapData, 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 SizeF PrintOrMeasure(string text, QFontAlignment alignment, bool measureOnly) { var popRequired = false; if (!measureOnly && !ProjectionStack.Begun && Options.TransformToViewport != null) { GL.PushMatrix(); popRequired = true; GL.Scale(1 / fontData.scaleDueToTransformToViewport, 1 / fontData.scaleDueToTransformToViewport, 0); } float maxXpos = float.MinValue; float minXPos = float.MaxValue; if (!measureOnly) { GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); GL.Enable(EnableCap.Texture2D); GL.Enable(EnableCap.Blend); if (Options.UseDefaultBlendFunction) { GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); } } float xOffset = 0f; float yOffset = 0f; text = text.Replace("\r\n", "\r"); if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text)); } for (int i = 0; i < text.Length; i++) { char c = text[i]; //newline if (c == '\r' || c == '\n') { yOffset += LineSpacing; xOffset = 0f; if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text.Substring(i + 1)); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text.Substring(i + 1))); } } else { minXPos = Math.Min(xOffset, minXPos); //normal character if (c != ' ' && fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; if (!measureOnly) { RenderGlyph(xOffset, yOffset, c, false); } } if (IsMonospacingActive) { xOffset += MonoSpaceWidth; } else { if (c == ' ') { xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * Options.WordSpacing); } //normal character else if (fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); } } maxXpos = Math.Max(xOffset, maxXpos); } } float maxWidth = 0f; if (minXPos != float.MaxValue) { maxWidth = maxXpos - minXPos; } if (popRequired) { GL.PopMatrix(); } return(new SizeF(maxWidth, yOffset + LineSpacing)); }
private static List<QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) { var pages = new List<QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap 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 QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(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) QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); else QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); destGlyphs[i] = new QFontGlyph(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 Dictionary<char, QFontGlyph> CreateCharGlyphMapping(QFontGlyph[] glyphs) { var dict = new Dictionary<char, QFontGlyph>(); for (int i = 0; i < charSet.Length; i++) dict.Add(charSet[i], glyphs[i]); return dict; }
private SizeF PrintOrMeasure(string text, QFontAlignment alignment, bool measureOnly, Rectangle clippingRectangle = default(Rectangle)) { float maxWidth = 0f; float xOffset = 0f; float yOffset = 0f; float maxXpos = float.MinValue; float minXPos = float.MaxValue; text = text.Replace("\r\n", "\r"); #if DEBUG _DisplayText_dbg = text; #endif if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text)); } for (int i = 0; i < text.Length; i++) { char c = text[i]; //newline if (c == '\r' || c == '\n') { yOffset += LineSpacing; xOffset = 0f; if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text.Substring(i + 1)); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text.Substring(i + 1))); } } else { minXPos = Math.Min(xOffset, minXPos); //normal character if (c != ' ' && _font.FontData.CharSetMapping.ContainsKey(c)) { if (!measureOnly) { RenderGlyph(xOffset, yOffset, c, _font, CurrentVertexRepr, clippingRectangle); } } if (IsMonospacingActive) { xOffset += MonoSpaceWidth; } else { if (c == ' ') { xOffset += (float)Math.Ceiling(_font.FontData.meanGlyphWidth * this.Options.WordSpacing); } //normal character else if (_font.FontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = _font.FontData.CharSetMapping[c]; xOffset += (float) Math.Ceiling(glyph.rect.Width + _font.FontData.meanGlyphWidth * this.Options.CharacterSpacing + _font.FontData.GetKerningPairCorrection(i, text, null)); } } maxXpos = Math.Max(xOffset, maxXpos); } } if (minXPos != float.MaxValue) { maxWidth = maxXpos - minXPos; } LastSize = new SizeF(maxWidth, yOffset + LineSpacing); return(LastSize); }
private void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph, QFont shadowFont, ref Rectangle clippingRectangle) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (shadowFont != null && this.Options.DropShadowActive) { float xOffset = (_font.FontData.meanGlyphWidth*this.Options.DropShadowOffset.X + nonShadowGlyph.rect.Width*0.5f); float yOffset = (_font.FontData.meanGlyphWidth*this.Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height*0.5f + nonShadowGlyph.yOffset); this.RenderGlyph(x + xOffset, y + yOffset, c, shadowFont, this.ShadowVertexRepr, clippingRectangle); } }
public static Dictionary<String, int> CalculateKerning(char[] charSet, QFontGlyph[] glyphs, List<QBitmap> bitmapPages, QFontKerningConfiguration 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 (!QBitmap.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 SizeF GetMaxGlyphSize(Font font) { Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb); Graphics graph = Graphics.FromImage(bmp); SizeF maxSize = new SizeF(0f, 0f); for (int i = 0; i < charSet.Length; i++) { var charSize = graph.MeasureString("" + charSet[i], font); if (charSize.Width > maxSize.Width) maxSize.Width = charSize.Width; if (charSize.Height > maxSize.Height) maxSize.Height = charSize.Height; } graph.Dispose(); bmp.Dispose(); return maxSize; }*/ //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 QFontGlyph[] glyphs, TextGenerationRenderHint renderHint) { glyphs = new QFontGlyph[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); Graphics graph = 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 QFontGlyph(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 int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2) { 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; //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 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); return worstCase; }
/// <summary> /// Generates the final bitmap sheet for the font /// </summary> /// <param name="sourceGlyphs">A collection of <see cref="QFontGlyph"/>s. These are written to the final bitmap</param> /// <param name="sourceBitmaps"> The source bitmaps for the font (initial bitmap)</param> /// <param name="destSheetWidth">The destination bitmap width</param> /// <param name="destSheetHeight">The destination bitmap height</param> /// <param name="destGlyphs">A collection of <see cref="QFontGlyph"/>s that are placed on the final bitmap sheet</param> /// <param name="destMargin">The margin for the final bitmap sheet</param> /// <returns>A collection of <see cref="QBitmap"/>s. These are the final bitmap sheets</returns> private static List <QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin) { var pages = new List <QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap 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; // We loop through the whole process twice. The first time is to determine // the size of the final page, so that we can crop it in advance for (int k = 0; k < 2; k++) { // Whether this is the pre-processing step bool pre = k == 0; int xPos = 0; int yPos = 0; int maxYInRow = 0; int totalTries = 0; // Loop through all the glyphs for (int i = 0; i < sourceGlyphs.Length; i++) { // If this is the second stage and we don't already have a bitmap page, create one if (!pre && currentPage == null) { if (finalPageIndex == pages.Count) { int width = Math.Min(destSheetWidth, finalPageRequiredWidth); int height = Math.Min(destSheetHeight, finalPageRequiredHeight); currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } pages.Add(currentPage); } // Keep track of the number of times we've tried to fit the font onto the texture page totalTries++; if (totalTries > 10 * sourceGlyphs.Length) { throw new Exception("Failed to fit font into texture pages"); } var rect = sourceGlyphs[i].Rect; // If we can fit the glyph onto the page, place it 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) { QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].Page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } else { QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].Page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } // Add to destination glyph collection destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].YOffset, sourceGlyphs[i].Character); } else { // Update the final dimensions finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); } // Update the current x position xPos += rect.Width + 2 * destMargin; // Update the maximum row height so far maxYInRow = Math.Max(maxYInRow, rect.Height); continue; } // If we reach this, haven't been able to fit glyph onto row // Move down one row and try again if (xPos + rect.Width + 2 * destMargin > destSheetWidth) { // Retry the current glyph on the next row i--; // Change coordinates to next row yPos += maxYInRow + 2 * destMargin; xPos = 0; // Is the next row off the bitmap sheet? if (yPos + maxY + 2 * destMargin > destSheetHeight) { // Reset y position yPos = 0; if (!pre) { // If this is not the second stage, reset the currentPage // This will create a new one on next loop currentPage = null; } else { // If this is the pre-processing stage, update // the finalPageIndex. Reset width and height // since we clearly need one full page and extra finalPageRequiredWidth = 0; finalPageRequiredHeight = 0; finalPageIndex++; } } } } } return(pages); }
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset) { 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(QBitmap.EmptyAlphaPixel(data, x, y)); } } ; else { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.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 :; } glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) { glyph.yOffset = glyph.rect.Y; } }
private void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (fontData.dropShadow != null && Options.DropShadowActive) { fontData.dropShadow.RenderGlyph( x + (fontData.meanGlyphWidth * Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f), y + (fontData.meanGlyphWidth * Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset), c); } }
/// <summary> /// Renders the glyph at the position given. /// </summary> /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <param name="c">The character to print.</param> internal void RenderGlyph(float x, float y, char c, QFont font, IList <QVertex> store, Rectangle clippingRectangle) { QFontGlyph glyph = font.FontData.CharSetMapping[c]; //note: it's not immediately obvious, but this combined with the paramteters to //RenderGlyph for the shadow mean that we render the shadow centrally (despite it being a different size) //under the glyph if (font.FontData.isDropShadow) { x -= (int)(glyph.rect.Width * 0.5f); y -= (int)(glyph.rect.Height * 0.5f + glyph.yOffset); } else { RenderDropShadow(x, y, c, glyph, font.FontData.dropShadowFont, ref clippingRectangle); } y = -y; TexturePage sheet = font.FontData.Pages[glyph.page]; float tx1 = (float)(glyph.rect.X) / sheet.Width; float ty1 = (float)(glyph.rect.Y) / sheet.Height; float tx2 = (float)(glyph.rect.X + glyph.rect.Width) / sheet.Width; float ty2 = (float)(glyph.rect.Y + glyph.rect.Height) / sheet.Height; float vx = x + PrintOffset.X; float vy = y - glyph.yOffset + PrintOffset.Y; float vwidth = glyph.rect.Width; float vheight = glyph.rect.Height; if (clippingRectangle != default(Rectangle) && ScissorsTest(ref vx, ref vy, ref vwidth, ref vheight, ref tx1, ref ty1, ref tx2, ref ty2, clippingRectangle)) { return; } var tv1 = new Vector2(tx1, ty1); var tv2 = new Vector2(tx1, ty2); var tv3 = new Vector2(tx2, ty2); var tv4 = new Vector2(tx2, ty1); Vector3 v1 = new Vector3(vx, vy, PrintOffset.Z); Vector3 v2 = new Vector3(vx, vy - vheight, PrintOffset.Z); Vector3 v3 = new Vector3(vx + vwidth, vy - vheight, PrintOffset.Z); Vector3 v4 = new Vector3(vx + vwidth, vy, PrintOffset.Z); Color color; if (font.FontData.isDropShadow) { color = this.Options.DropShadowColour; } else { color = this.Options.Colour; } Vector4 colour = Helper.ToVector4(color); store.Add(new QVertex() { Position = v1, TextureCoord = tv1, VertexColor = colour }); store.Add(new QVertex() { Position = v2, TextureCoord = tv2, VertexColor = colour }); store.Add(new QVertex() { Position = v3, TextureCoord = tv3, VertexColor = colour }); store.Add(new QVertex() { Position = v1, TextureCoord = tv1, VertexColor = colour }); store.Add(new QVertex() { Position = v3, TextureCoord = tv3, VertexColor = colour }); store.Add(new QVertex() { Position = v4, TextureCoord = tv4, VertexColor = colour }); }
private SizeF PrintOrMeasure(string text, QFontAlignment alignment, bool measureOnly) { float maxWidth = 0f; float xOffset = 0f; float yOffset = 0f; var caps = new EnableCap[] { }; if (!UsingVertexBuffers) { caps = new EnableCap[] { EnableCap.Texture2D, EnableCap.Blend } } ; Helper.SafeGLEnable(caps, () => { float maxXpos = float.MinValue; float minXPos = float.MaxValue; if (!UsingVertexBuffers) { GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); if (Options.UseDefaultBlendFunction) { GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); } } text = text.Replace("\r\n", "\r"); if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text)); } for (int i = 0; i < text.Length; i++) { char c = text[i]; //newline if (c == '\r' || c == '\n') { yOffset += LineSpacing; xOffset = 0f; if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text.Substring(i + 1)); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text.Substring(i + 1))); } } else { minXPos = Math.Min(xOffset, minXPos); //normal character if (c != ' ' && fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; if (!measureOnly) { RenderGlyph(xOffset, yOffset, c, false); } } if (IsMonospacingActive) { xOffset += MonoSpaceWidth; } else { if (c == ' ') { xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * Options.WordSpacing); } //normal character else if (fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); } } maxXpos = Math.Max(xOffset, maxXpos); } } if (minXPos != float.MaxValue) { maxWidth = maxXpos - minXPos; } }); return(new SizeF(maxWidth, yOffset + LineSpacing)); }
/* private SizeF GetMaxGlyphSize(Font font) { Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb); Graphics graph = Graphics.FromImage(bmp); SizeF maxSize = new SizeF(0f, 0f); for (int i = 0; i < charSet.Length; i++) { var charSize = graph.MeasureString("" + charSet[i], font); if (charSize.Width > maxSize.Width) maxSize.Width = charSize.Width; if (charSize.Height > maxSize.Height) maxSize.Height = charSize.Height; } graph.Dispose(); bmp.Dispose(); return maxSize; }*/ private Bitmap CreateInitialBitmap(Font font, SizeF maxSize, int initialMargin, out QFontGlyph[] glyphs) { glyphs = new QFontGlyph[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); Graphics graph = Graphics.FromImage(bmp); graph.TextRenderingHint = TextRenderingHint.AntiAlias; 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 QFontGlyph(0, new Rectangle(xOffset - initialMargin, 0, (int)charSize.Width + initialMargin*2, (int)charSize.Height + initialMargin*2), 0); xOffset += (int)charSize.Width + initialMargin * 2; } graph.Flush(); graph.Dispose(); return bmp; }
private static QFont BuildDropShadow(List<QBitmap> sourceFontSheets, QFontGlyph[] sourceFontGlyphs, QFontShadowConfiguration shadowConfig, char[] charSet, byte alphaTolerance) { QFontGlyph[] newGlyphs; var sourceBitmapData = new List<BitmapData>(); foreach(var sourceSheet in sourceFontSheets) sourceBitmapData.Add(sourceSheet.bitmapData); var bitmapSheets = GenerateBitmapSheetsAndRepack(sourceFontGlyphs, sourceBitmapData.ToArray(), shadowConfig.PageMaxTextureSize, shadowConfig.PageMaxTextureSize, out newGlyphs, shadowConfig.GlyphMargin + shadowConfig.blurRadius*3); //scale up in case we wanted bigger/smaller shadows if (shadowConfig.Scale != 1.0f) ScaleSheetsAndGlyphs(bitmapSheets, newGlyphs, shadowConfig.Scale); //no point in retargeting yet, since we will do it after blur //whiten and blur foreach (var bitmapSheet in bitmapSheets) { bitmapSheet.Colour32(255, 255, 255); if (shadowConfig.Type == ShadowType.Blurred) bitmapSheet.BlurAlpha(shadowConfig.blurRadius, shadowConfig.blurPasses); else bitmapSheet.ExpandAlpha(shadowConfig.blurRadius, shadowConfig.blurPasses); } //retarget after blur and scale RetargetAllGlyphs(bitmapSheets, newGlyphs, alphaTolerance); //create list of texture pages var newTextureSheets = new List<TexturePage>(); foreach (var page in bitmapSheets) newTextureSheets.Add(new TexturePage(page.bitmapData)); var fontData = new QFontData(); fontData.CharSetMapping = new Dictionary<char, QFontGlyph>(); for(int i = 0; i < charSet.Length; i++) fontData.CharSetMapping.Add(charSet[i],newGlyphs[i]); fontData.Pages = newTextureSheets.ToArray(); fontData.CalculateMeanWidth(); fontData.CalculateMaxHeight(); foreach (var sheet in bitmapSheets) sheet.Free(); fontData.isDropShadow = true; return new QFont(fontData); }
private static void RetargetAllGlyphs(List<QBitmap> pages, QFontGlyph[] glyphs, byte alphaTolerance) { foreach (var glyph in glyphs) RetargetGlyphRectangleOutwards(pages[glyph.page].BitmapData, glyph, false, alphaTolerance); }
private static QFontData BuildDropShadow(List<QBitmap> sourceFontSheets, QFontGlyph[] sourceFontGlyphs, QFontShadowConfiguration shadowConfig, char[] charSet, byte alphaTolerance) { QFontGlyph[] newGlyphs; var sourceBitmapData = new List<BitmapData>(); foreach(var sourceSheet in sourceFontSheets) sourceBitmapData.Add(sourceSheet.BitmapData); //GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) var bitmapSheets = GenerateBitmapSheetsAndRepack(sourceFontGlyphs, sourceBitmapData.ToArray(), shadowConfig.PageWidth, shadowConfig.PageHeight, out newGlyphs, shadowConfig.GlyphMargin + shadowConfig.blurRadius*3, shadowConfig.ForcePowerOfTwo); //scale up in case we wanted bigger/smaller shadows if (shadowConfig.Scale != 1.0f) ScaleSheetsAndGlyphs(bitmapSheets, newGlyphs, shadowConfig.Scale); //no point in retargeting yet, since we will do it after blur //blacken and blur foreach (var bitmapSheet in bitmapSheets) { bitmapSheet.Colour32(0, 0, 0); bitmapSheet.BlurAlpha(shadowConfig.blurRadius, shadowConfig.blurPasses); } //retarget after blur and scale RetargetAllGlyphs(bitmapSheets, newGlyphs, alphaTolerance); var fontData = new QFontData(); fontData.CharSetMapping = new Dictionary<char, QFontGlyph>(); for(int i = 0; i < charSet.Length; i++) fontData.CharSetMapping.Add(charSet[i],newGlyphs[i]); fontData.Pages = bitmapSheets.ToArray(); fontData.CalculateMeanWidth(); fontData.CalculateMaxHeight(); return fontData; }
private void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph) { //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... if (fontData.dropShadow != null && Options.DropShadowActive) { //make sure fontdata font's options are synced with the actual options if (fontData.dropShadow.Options != Options) fontData.dropShadow.Options = Options; fontData.dropShadow.RenderGlyph( x + (fontData.meanGlyphWidth * Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f), y + (fontData.meanGlyphWidth * Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset), c, true); } }
private static void ScaleSheetsAndGlyphs(List<QBitmap> pages, QFontGlyph[] 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 SizeF PrintOrMeasure(string text, QFontAlignment alignment, bool measureOnly) { float maxWidth = 0f; GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); GL.Enable(EnableCap.Texture2D); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); float xOffset = 0f; float yOffset = 0f; text = text.Replace("\r\n", "\r"); if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text)); } for (int i = 0; i < text.Length; i++) { char c = text[i]; //newline if (c == '\r' || c == '\n') { yOffset += LineSpacing; xOffset = 0f; if (alignment == QFontAlignment.Right) { xOffset -= MeasureNextlineLength(text.Substring(i + 1)); } else if (alignment == QFontAlignment.Centre) { xOffset -= (int)(0.5f * MeasureNextlineLength(text.Substring(i + 1))); } } else { //normal character if (c != ' ' && fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; if (!measureOnly) { RenderGlyph(xOffset, yOffset, c, false); } } if (IsMonospacingActive) { xOffset += MonoSpaceWidth; } else { if (c == ' ') { xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * options.WordSpacing); } //normal character else if (fontData.CharSetMapping.ContainsKey(c)) { QFontGlyph glyph = fontData.CharSetMapping[c]; xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); } } maxWidth = Math.Max(xOffset, maxWidth); } } return(new SizeF(maxWidth, yOffset + LineSpacing)); }