private void ForEachGlyph <T>(ref StringProxy text, ref Vector2 fontSize, GlyphAction <T> action, ref T parameters, TextAlignment scanOrder, bool updateGpuResources, Vector2?elementsize = null) { if (scanOrder == TextAlignment.Left) { // scan the whole text only one time following the text letter order ForGlyph(ref text, ref fontSize, action, ref parameters, 0, text.Length, updateGpuResources); } else // scan the text line by line incrementing y start position { // measure the whole string in order to be able to determine xStart var wholeSize = elementsize.HasValue ? elementsize.Value : MeasureString(ref text, ref fontSize); // scan the text line by line var yStart = 0f; var startIndex = 0; var endIndex = FindCariageReturn(ref text, 0); while (startIndex < text.Length) { // measure the size of the current line var lineSize = Vector2.Zero; ForGlyph(ref text, ref fontSize, MeasureStringGlyph, ref lineSize, startIndex, endIndex, updateGpuResources); // scan the line var xStart = (scanOrder == TextAlignment.Center) ? (wholeSize.X - lineSize.X) / 2 : wholeSize.X - lineSize.X; ForGlyph(ref text, ref fontSize, action, ref parameters, startIndex, endIndex, updateGpuResources, xStart, yStart); // update variable before going to next line yStart += GetTotalLineSpacing(fontSize.Y); startIndex = endIndex + 1; endIndex = FindCariageReturn(ref text, startIndex); } } }
private void ForGlyph <T>(ref StringProxy text, ref Vector2 fontSize, GlyphAction <T> action, ref T parameters, int forStart, int forEnd, bool updateGpuResources, float startX = 0, float startY = 0) { var key = 0; var x = startX; var y = startY; for (var i = forStart; i < forEnd; i++) { char character = text[i]; switch (character) { case '\r': // Skip carriage returns. key |= character; continue; case '\n': // New line. x = 0; y += GetTotalLineSpacing(fontSize.Y); key |= character; break; default: // Output this character. var glyph = GetGlyph(character, ref fontSize, updateGpuResources); if (glyph == null && !IgnoreUnkownCharacters && DefaultCharacter.HasValue) { glyph = GetGlyph(DefaultCharacter.Value, ref fontSize, updateGpuResources); } if (glyph == null) { continue; } key |= character; float dx = glyph.Offset.X; float kerningOffset; if (KerningMap != null && KerningMap.TryGetValue(key, out kerningOffset)) { dx += kerningOffset; } float nextX = x + glyph.XAdvance + GetExtraSpacing(fontSize.X); action(ref parameters, ref fontSize, ref glyph, x + dx, y, nextX); x = nextX; break; } // Shift the kerning key key = (key << 16); } }
private void ForEachGlyph <T>(ref StringProxy text, ref Vector2 fontSize, GlyphAction <T> action, ref T parameters, TextAlignment scanOrder, bool updateGpuResources, Vector2?elementsize = null) { if (scanOrder == TextAlignment.Left) { // scan the whole text only one time following the text letter order ForGlyph(ref text, ref fontSize, action, ref parameters, 0, text.Length, updateGpuResources); } else // scan the text line by line incrementing y start position { // measure the whole string in order to be able to determine xStart var wholeSize = elementsize ?? MeasureString(ref text, ref fontSize); // scan the text line by line var yStart = 0f; var startIndex = 0; var endIndex = FindCariageReturn(ref text, 0); while (startIndex < text.Length) { // measure the size of the current line var lineSize = Vector2.Zero; ForGlyph(ref text, ref fontSize, MeasureStringGlyph, ref lineSize, startIndex, endIndex, updateGpuResources); // Determine the start position of the line along the x axis // We round this value to the closest integer to force alignment of all characters to the same pixels // Otherwise the starting offset can fall just in between two pixels and due to float imprecision // some characters can be aligned to the pixel before and others to the pixel after, resulting in gaps and character overlapping var xStart = (scanOrder == TextAlignment.Center) ? (wholeSize.X - lineSize.X) / 2 : wholeSize.X - lineSize.X; xStart = (float)Math.Round(xStart); // scan the line ForGlyph(ref text, ref fontSize, action, ref parameters, startIndex, endIndex, updateGpuResources, xStart, yStart); // update variable before going to next line yStart += GetTotalLineSpacing(fontSize.Y); startIndex = endIndex + 1; endIndex = FindCariageReturn(ref text, startIndex); } } }
protected internal SpriteFont() { internalDrawGlyphAction = InternalDrawGlyph; internalUIDrawGlyphAction = InternalUIDrawGlyph; measureStringGlyphAction = MeasureStringGlyph; }
private unsafe void ForEachGlyph <T>(ref StringProxy text, GlyphAction <T> action, ref T parameters) { float x = 0; float y = 0; // TODO: Not sure how to handle globalBaseOffsetY from AngelCode BMFont fixed(void *pGlyph = glyphs) { var key = 0; for (int i = 0; i < text.Length; i++) { char character = text[i]; switch (character) { case '\r': // Skip carriage returns. key |= character; continue; case '\n': // New line. x = 0; y += LineSpacing; key |= character; break; default: // Output this character. int glyphIndex; if (!characterMap.TryGetValue(character, out glyphIndex)) { if (IgnoreUnkownCharacters) { continue; } if (DefaultCharacter.HasValue && defaultGlyphIndex >= 0) { character = DefaultCharacter.Value; glyphIndex = defaultGlyphIndex; } else { throw new ArgumentException(string.Format("Character '{0}' is not available in the SpriteFont character map", character), "text"); } } key |= character; var glyph = (SpriteFontData.Glyph *)pGlyph + glyphIndex; // do not offset the first character, otherwise it is impossible to compute correct alignment // using MeasureString results if (x > 0f) { x += glyph->Offset.X; } // reset negative offset (it can happen only for first character) if (x < 0f) { x = 0f; } // Offset the kerning float kerningOffset; if (kerningMap != null && kerningMap.TryGetValue(key, out kerningOffset)) { x += kerningOffset; } if (!char.IsWhiteSpace(character)) { action(ref parameters, ref *glyph, x, y); } x += glyph->XAdvance + Spacing; break; } // Shift the kerning key key = (key << 16); } } }
internal SpriteFont(GraphicsDevice device, SpriteFontData spriteFontData, bool disposeSpriteFontDataResources) : base(device) { drawGlyphDelegate = InternalDrawGlyph; measureGlyphDelegate = MeasureStringGlyph; // Read the glyph data. globalBaseOffsetY = spriteFontData.BaseOffset; glyphs = spriteFontData.Glyphs; characterMap = new Dictionary <char, int>(glyphs.Length * 2); // Prebuild the character map var characterList = new List <char>(glyphs.Length); for (int i = 0; i < glyphs.Length; i++) { var charItem = (char)glyphs[i].Character; characterMap.Add(charItem, i); characterList.Add(charItem); } // Prepare kernings if they are available. var kernings = spriteFontData.Kernings; if (kernings != null) { kerningMap = new Dictionary <int, float>(spriteFontData.Kernings.Length); for (int i = 0; i < kernings.Length; i++) { int key = (kernings[i].First << 16) | kernings[i].Second; kerningMap.Add(key, kernings[i].Offset); } } Characters = new ReadOnlyCollection <char>(characterList); // Read font properties. LineSpacing = spriteFontData.LineSpacing; if (spriteFontData.DefaultCharacter > 0) { DefaultCharacter = (char)spriteFontData.DefaultCharacter; if (!characterMap.TryGetValue(DefaultCharacter.Value, out defaultGlyphIndex)) { defaultGlyphIndex = -1; } } // Read the texture data. textures = new Texture2D[spriteFontData.Bitmaps.Length]; for (int i = 0; i < textures.Length; i++) { var bitmap = spriteFontData.Bitmaps[i]; if (bitmap.Data is SpriteFontData.BitmapData) { var image = (SpriteFontData.BitmapData)bitmap.Data; textures[i] = ToDispose(Texture2D.New(device, image.Width, image.Height, image.PixelFormat, image.Data)); } else if (bitmap.Data is Texture2D) { textures[i] = (Texture2D)bitmap.Data; if (disposeSpriteFontDataResources) { ToDispose(textures[i]); } } else { throw new NotSupportedException(string.Format("SpriteFontData.Bitmap of type [{0}] is not supported. Only SpriteFontData.BitmapData or Texture2D", bitmap == null ? "null" : bitmap.GetType().Name)); } } }
private void ForGlyph <T>(CommandList commandList, ref StringProxy text, ref Vector2 fontSize, GlyphAction <T> action, ref T parameters, int forStart, int forEnd, bool updateGpuResources, float startX = 0, float startY = 0, TextVerticalAlignment vertAlign = TextVerticalAlignment.Top, float fontSizeY = 0f) { var key = 0; var x = startX; var y = startY; // tag management var escaping = false; colorStack.Clear(); for (var i = forStart; i < forEnd; i++) { var character = text[i]; if (!escaping && character == '<') { // check tags if (CheckAndProcessColorTag(ref text, ref i, out Color4 color)) { colorStack.Push(color); } else if (colorStack.Count > 0 && EndsTag("</color>", ref text, ref i)) { colorStack.Pop(); } }
private void ForEachGlyph <T>(CommandList commandList, ref StringProxy text, ref Vector2 requestedFontSize, GlyphAction <T> action, ref T parameters, TextAlignment scanOrder, TextVerticalAlignment vertAlign, bool updateGpuResources, Vector2?textBoxSize = null, float lineSpaceAdjustment = 0f) { float rawYSpacing = GetTotalLineSpacing(requestedFontSize.Y); float yStart, ySpacing = rawYSpacing + lineSpaceAdjustment; if (textBoxSize.HasValue && vertAlign != TextVerticalAlignment.Top) { int extraLines = text.LineCount - 1; float lineHeight = rawYSpacing + extraLines * ySpacing; switch (vertAlign) { default: case TextVerticalAlignment.Center: yStart = textBoxSize.Value.Y * 0.5f - (lineHeight * 0.5f); break; case TextVerticalAlignment.Bottom: yStart = textBoxSize.Value.Y - lineHeight; break; } } else { yStart = 0f; } if (scanOrder == TextAlignment.Left) { // scan the whole text only one time following the text letter order ForGlyph(commandList, ref text, ref requestedFontSize, action, ref parameters, 0, text.Length, updateGpuResources, 0f, yStart, vertAlign, ySpacing); } else { // scan the text line by line incrementing y start position // measure the whole string in order to be able to determine xStart var wholeSize = textBoxSize ?? MeasureString(ref text, ref requestedFontSize); // scan the text line by line var startIndex = 0; var endIndex = FindCariageReturn(ref text, 0); while (startIndex < text.Length) { // measure the size of the current line var lineSize = Vector2.Zero; ForGlyph(commandList, ref text, ref requestedFontSize, MeasureStringGlyph, ref lineSize, startIndex, endIndex, updateGpuResources, 0f, 0f, vertAlign, ySpacing); // Determine the start position of the line along the x axis // We round this value to the closest integer to force alignment of all characters to the same pixels // Otherwise the starting offset can fall just in between two pixels and due to float imprecision // some characters can be aligned to the pixel before and others to the pixel after, resulting in gaps and character overlapping var xStart = (scanOrder == TextAlignment.Center) ? (wholeSize.X - lineSize.X) / 2 : wholeSize.X - lineSize.X; xStart = (float)Math.Round(xStart); // scan the line ForGlyph(commandList, ref text, ref requestedFontSize, action, ref parameters, startIndex, endIndex, updateGpuResources, xStart, yStart, vertAlign, ySpacing); // update variable before going to next line yStart += ySpacing; startIndex = endIndex + 1; endIndex = FindCariageReturn(ref text, startIndex); } } }
private unsafe void ForEachGlyph <T>(ref StringProxy text, GlyphAction <T> action, ref T parameters) { float x = 0; float y = 0; // TODO: Not sure how to handle globalBaseOffsetY from AngelCode BMFont fixed(void *pGlyph = glyphs) { var key = 0; for (int i = 0; i < text.Length; i++) { char character = text[i]; switch (character) { case '\r': // Skip carriage returns. key |= character; continue; case '\n': // New line. x = 0; y += LineSpacing; key = 0; break; default: // Output this character. int glyphIndex; if (!characterMap.TryGetValue(character, out glyphIndex)) { if (IgnoreUnkownCharacters) { continue; } if (DefaultCharacter.HasValue && defaultGlyphIndex >= 0) { character = DefaultCharacter.Value; glyphIndex = defaultGlyphIndex; } else { throw new ArgumentException(string.Format("Character '{0}' is not available in the SpriteFont character map", character), "text"); } } key |= character; var glyph = (SpriteFontData.Glyph *)pGlyph + glyphIndex; float dx = glyph->Offset.X; float kerningOffset; if (kerningMap != null && kerningMap.TryGetValue(key, out kerningOffset)) { dx += kerningOffset; } float nextX = x + glyph->XAdvance + Spacing; action(ref parameters, ref *glyph, x + dx, y, nextX); x = nextX; break; } // Shift the kerning key key = (key << 16); } } }
private unsafe void ForEachGlyph(ref StringProxy text, GlyphAction action) { float x = 0; float y = 0; // TODO: Not sure how to handle globalBaseOffsetY from AngelCode BMFont fixed (void* pGlyph = glyphs) { var key = 0; for (int i = 0; i < text.Length; i++) { char character = text[i]; switch (character) { case '\r': // Skip carriage returns. key |= character; continue; case '\n': // New line. x = 0; y += LineSpacing; key |= character; break; default: // Output this character. int glyphIndex; if (!characterMap.TryGetValue(character, out glyphIndex)) { if(DefaultCharacter.HasValue && defaultGlyphIndex >= 0) { character = DefaultCharacter.Value; glyphIndex = defaultGlyphIndex; } else { throw new ArgumentException(string.Format("Character '{0}' is not available in the SpriteFont character map", character), "text"); } } key |= character; var glyph = (SpriteFontData.Glyph*) pGlyph + glyphIndex; x += glyph->Offset.X; if (x < 0) x = 0; // Offset the kerning float kerningOffset; if (kerningMap != null && kerningMap.TryGetValue(key, out kerningOffset)) x += kerningOffset; if (!char.IsWhiteSpace(character)) { action(ref *glyph, x, y); } x += glyph->XAdvance; break; } // Shift the kerning key key = (key << 16); } } }
private void ForGlyph <T>(CommandList commandList, ref StringProxy text, ref Vector2 fontSize, GlyphAction <T> action, ref T parameters, int forStart, int forEnd, bool updateGpuResources, float startX = 0, float startY = 0, TextVerticalAlignment vertAlign = TextVerticalAlignment.Top, float fontSizeY = 0f) { var key = 0; var x = startX; var y = startY; // color tags var escaping = false; var tagStack = new List <Color4>(); for (var i = forStart; i < forEnd; i++) { var character = text[i]; if (StartsColorTag(ref text, ref i, out Color4 color) && !escaping) { tagStack.Add(color); }