2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
9 using System.Threading;
10 using System.Xml.Linq;
17 private static readonly List<ScalableFont> FontList =
new List<ScalableFont>();
18 private static Library Lib =
null;
19 private static readonly
object globalMutex =
new object();
21 private readonly ReaderWriterLockSlim rwl =
new ReaderWriterLockSlim();
23 private readonly
string filename;
24 private readonly Face face;
26 private int baseHeight;
27 private readonly Dictionary<uint, GlyphData> texCoords;
28 private readonly List<Texture2D> textures;
29 private readonly GraphicsDevice graphicsDevice;
31 private Vector2 currentDynamicAtlasCoords;
32 private int currentDynamicAtlasNextY;
33 uint[] currentDynamicPixelBuffer;
56 if (graphicsDevice !=
null) { RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); }
64 private uint[] charRanges;
66 private uint baseChar;
69 int TexIndex =
default,
70 Vector2 DrawOffset =
default,
75 => TextManager.SpeciallyHandledCharCategories
76 .Where(category => element.GetAttributeBool($
"is{category}", category
switch {
78 TextManager.SpeciallyHandledCharCategory.CJK => false,
79 TextManager.SpeciallyHandledCharCategory.Japanese => false,
82 TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
84 _ => throw new NotImplementedException($
"nameof{category} not implemented.")
86 .Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
90 element.GetAttributeContentPath(
"file")?.Value,
91 (uint)element.GetAttributeInt(
"size", (int)defaultSize),
93 element.GetAttributeBool(
"dynamicloading", false),
101 GraphicsDevice gd =
null,
102 bool dynamicLoading =
false,
103 TextManager.SpeciallyHandledCharCategory speciallyHandledCharCategory = TextManager.SpeciallyHandledCharCategory.None)
107 Lib ??=
new Library();
110 this.filename = filename;
112 using (
new ReadLock(rwl))
116 if (font.filename == filename)
118 this.face = font.face;
124 this.face ??=
new Face(Lib, filename);
126 this.textures =
new List<Texture2D>();
127 this.texCoords =
new Dictionary<uint, GlyphData>();
130 this.graphicsDevice = gd;
132 if (gd !=
null && !dynamicLoading)
151 private void RenderAtlas(GraphicsDevice gd, uint[] charRanges =
null,
int texDims = 1024, uint baseChar = 0x54)
155 if (charRanges ==
null)
157 charRanges =
new uint[] { 0x20, 0xFFFF };
159 this.charRanges = charRanges;
160 this.texDims = texDims;
161 this.baseChar = baseChar;
163 textures.ForEach(t => t.Dispose());
167 uint[] pixelBuffer =
new uint[texDims * texDims];
168 for (
int i = 0; i < texDims * texDims; i++)
173 CrossThread.RequestExecutionOnMainThread(() =>
175 textures.Add(
new Texture2D(gd, texDims, texDims,
false, SurfaceFormat.Color));
179 Vector2 currentCoords = Vector2.Zero;
182 using (
new WriteLock(rwl))
184 face.SetPixelSizes(0, size);
185 face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
186 baseHeight = face.Glyph.Metrics.Height.ToInt32();
188 for (
int i = 0; i < charRanges.Length; i += 2)
190 uint start = charRanges[i];
191 uint end = charRanges[i + 1];
192 for (uint j = start; j <= end; j++)
194 uint glyphIndex = face.GetCharIndex(j);
202 face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
203 if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
207 Advance: Math.Max((
float)face.Glyph.Metrics.HorizontalAdvance, 0f),
210 texCoords.Add(j, blankData);
214 face.Glyph.RenderGlyph(RenderMode.Normal);
215 byte[] bitmap = face.Glyph.Bitmap.BufferData;
216 int glyphWidth = face.Glyph.Bitmap.Width;
217 int glyphHeight = bitmap.Length / glyphWidth;
221 if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
223 throw new Exception(filename +
", " + size.ToString() +
", " + (
char)j +
"; Glyph dimensions exceed texture atlas dimensions");
226 nextY = Math.Max(nextY, glyphHeight + 2);
228 if (currentCoords.X + glyphWidth + 2 > texDims - 1)
231 currentCoords.Y += nextY;
234 if (currentCoords.Y + glyphHeight + 2 > texDims - 1)
238 CrossThread.RequestExecutionOnMainThread(() =>
240 textures[texIndex].SetData<uint>(pixelBuffer);
241 textures.Add(
new Texture2D(gd, texDims, texDims,
false, SurfaceFormat.Color));
244 for (
int k = 0; k < texDims * texDims; k++)
251 Advance: (
float)face.Glyph.Metrics.HorizontalAdvance,
253 TexCoords:
new Rectangle((
int)currentCoords.X, (
int)currentCoords.Y, glyphWidth, glyphHeight),
254 DrawOffset:
new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
256 texCoords.Add(j, newData);
258 for (
int y = 0; y < glyphHeight; y++)
260 for (
int x = 0; x < glyphWidth; x++)
262 byte byteColor = bitmap[x + y * glyphWidth];
263 pixelBuffer[((int)currentCoords.X + x) + ((int)currentCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
267 currentCoords.X += glyphWidth + 2;
269 CrossThread.RequestExecutionOnMainThread(() =>
271 textures[texIndex].SetData<uint>(pixelBuffer);
277 private void DynamicRenderAtlas(GraphicsDevice gd, uint character,
int texDims = 1024, uint baseChar = 0x54)
279 bool missingCharacterFound =
false;
280 using (
new ReadLock(rwl))
282 missingCharacterFound = !texCoords.ContainsKey(character);
284 if (!missingCharacterFound) {
return; }
285 DynamicRenderAtlas(gd, character.ToEnumerable(), texDims, baseChar);
288 private void DynamicRenderAtlas(GraphicsDevice gd,
string str,
int texDims = 1024, uint baseChar = 0x54)
290 bool missingCharacterFound =
false;
291 using (
new ReadLock(rwl))
293 foreach (var character
in str)
295 if (texCoords.ContainsKey(character)) {
continue; }
297 missingCharacterFound =
true;
301 if (!missingCharacterFound) {
return; }
302 DynamicRenderAtlas(gd, str.Select(c => (uint)c), texDims, baseChar);
305 private void DynamicRenderAtlas(GraphicsDevice gd, IEnumerable<uint> characters,
int texDims = 1024, uint baseChar = 0x54)
307 if (System.Threading.Thread.CurrentThread != GameMain.MainThread)
309 CrossThread.RequestExecutionOnMainThread(() =>
311 DynamicRenderAtlas(gd, characters, texDims, baseChar);
317 int glyphWidth;
int glyphHeight;
318 Fixed26Dot6 horizontalAdvance;
321 using (
new WriteLock(rwl))
323 if (textures.Count == 0)
325 this.texDims = texDims;
326 this.baseChar = baseChar;
327 face.SetPixelSizes(0, size);
328 face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
329 baseHeight = face.Glyph.Metrics.Height.ToInt32();
330 textures.Add(
new Texture2D(gd, texDims, texDims,
false, SurfaceFormat.Color));
333 bool anyChanges =
false;
334 bool firstChar =
true;
335 foreach (var character
in characters)
337 if (texCoords.ContainsKey(character)) {
continue; }
339 uint glyphIndex = face.GetCharIndex(character);
348 face.SetPixelSizes(0, size);
349 face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
350 if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
354 Advance: Math.Max((
float)face.Glyph.Metrics.HorizontalAdvance, 0f),
356 texCoords.Add(character, blankData);
361 face.Glyph.RenderGlyph(RenderMode.Normal);
362 bitmap = (
byte[])face.Glyph.Bitmap.BufferData.Clone();
363 glyphWidth = face.Glyph.Bitmap.Width;
364 glyphHeight = bitmap.Length / glyphWidth;
365 horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance;
366 drawOffset =
new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop);
368 if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
370 throw new Exception(filename +
", " + size.ToString() +
", " + (
char)character +
"; Glyph dimensions exceed texture atlas dimensions");
373 currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
374 if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
376 currentDynamicAtlasCoords.X = 0;
377 currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY;
378 currentDynamicAtlasNextY = 0;
381 if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
383 if (!firstChar) { textures[^1].SetData<uint>(currentDynamicPixelBuffer); }
384 currentDynamicAtlasCoords.X = 0;
385 currentDynamicAtlasCoords.Y = 0;
386 currentDynamicAtlasNextY = 0;
387 textures.Add(
new Texture2D(gd, texDims, texDims,
false, SurfaceFormat.Color));
388 currentDynamicPixelBuffer =
null;
392 Advance: (
float)horizontalAdvance,
393 TexIndex: textures.Count - 1,
394 TexCoords:
new Rectangle((
int)currentDynamicAtlasCoords.X, (
int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
395 DrawOffset: drawOffset
397 texCoords.Add(character, newData);
399 if (currentDynamicPixelBuffer ==
null)
401 currentDynamicPixelBuffer =
new uint[texDims * texDims];
402 textures[newData.TexIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
405 for (
int y = 0; y < glyphHeight; y++)
407 for (
int x = 0; x < glyphWidth; x++)
409 byte byteColor = bitmap[x + y * glyphWidth];
410 currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
414 currentDynamicAtlasCoords.X += glyphWidth + 2;
419 if (anyChanges) { textures[^1].SetData<uint>(currentDynamicPixelBuffer); }
424 private void HandleNewLineAndAlignment(
426 in Vector2 advanceUnit,
432 ref Vector2 currentLineOffset,
434 ref Vector2 currentPos,
436 out
bool shouldContinue)
438 if ((alignment.HasFlag(Alignment.CenterX) || alignment.HasFlag(Alignment.Right)) && (lineWidth < 0.0f || text[i] ==
'\n'))
440 int startIndex = lineWidth < 0.0f ? i : (i + 1);
442 for (
int j = startIndex; j < text.Length; j++)
444 if (text[j] ==
'\n') {
break; }
445 uint chrIndex = text[j];
447 var gd2 = GetGlyphData(chrIndex);
448 lineWidth += gd2.Advance;
450 currentLineOffset = -lineWidth * advanceUnit * scale.X;
451 if (alignment.HasFlag(Alignment.CenterX)) { currentLineOffset *= 0.5f; }
453 currentLineOffset.X = MathF.Round(currentLineOffset.X);
454 currentLineOffset.Y = MathF.Round(currentLineOffset.Y);
459 currentPos = position;
460 currentPos.X -=
LineHeight * lineNum * advanceUnit.Y * scale.Y;
461 currentPos.Y +=
LineHeight * lineNum * advanceUnit.X * scale.Y;
462 shouldContinue =
true; charIndex = 0;
return;
465 shouldContinue =
false;
469 private GlyphData GetGlyphData(uint charIndex)
471 const uint DEFAULT_INDEX = 0x25A1;
473 if (texCoords.TryGetValue(charIndex, out
GlyphData gd) ||
474 texCoords.TryGetValue(DEFAULT_INDEX, out gd))
482 public void DrawString(SpriteBatch sb,
string text, Vector2 position, Color color,
float rotation, Vector2 origin, Vector2 scale, SpriteEffects se,
float layerDepth, Alignment alignment = Alignment.TopLeft,
ForceUpperCase forceUpperCase =
Barotrauma.ForceUpperCase.Inherit)
485 text = ApplyUpperCase(text, forceUpperCase);
488 DynamicRenderAtlas(graphicsDevice, text);
491 float lineWidth = -1.0f;
492 Vector2 currentLineOffset = Vector2.Zero;
495 Vector2 currentPos = position;
496 Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX :
new Vector2((
float)Math.Cos(rotation), (
float)Math.Sin(rotation));
497 for (
int i = 0; i < text.Length; i++)
499 HandleNewLineAndAlignment(text, advanceUnit, position, scale, alignment, i,
500 ref lineWidth, ref currentLineOffset, ref lineNum, ref currentPos,
501 out uint charIndex, out
bool shouldContinue);
502 if (shouldContinue) {
continue; }
505 if (gd.TexIndex >= 0)
507 if (gd.TexIndex < 0 || gd.TexIndex >= textures.Count)
509 throw new ArgumentOutOfRangeException($
"Error while rendering text. Texture index was out of range. Text: {text}, char: {charIndex} index: {gd.TexIndex}, texture count: {textures.Count}");
511 Texture2D tex = textures[gd.TexIndex];
513 drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
514 drawOffset.Y = gd.DrawOffset.X * advanceUnit.Y * scale.Y + gd.DrawOffset.Y * advanceUnit.X * scale.X;
516 sb.Draw(tex, currentPos + currentLineOffset + drawOffset, gd.TexCoords, color, rotation, origin, scale, se, layerDepth);
518 currentPos += gd.Advance * advanceUnit * scale.X;
522 public void DrawString(SpriteBatch sb,
string text, Vector2 position, Color color,
float rotation, Vector2 origin,
float scale, SpriteEffects se,
float layerDepth, Alignment alignment = Alignment.TopLeft,
ForceUpperCase forceUpperCase =
Barotrauma.ForceUpperCase.Inherit)
524 DrawString(sb, text, position, color, rotation, origin,
new Vector2(scale), se, layerDepth, alignment, forceUpperCase);
527 private string ApplyUpperCase(
string text,
ForceUpperCase forceUpperCase)
528 => forceUpperCase
switch
531 Barotrauma.ForceUpperCase.Yes => text.ToUpperInvariant(),
535 private readonly
static VertexPositionColorTexture[] quadVertices =
new VertexPositionColorTexture[4];
539 text = ApplyUpperCase(text, forceUpperCase);
542 DynamicRenderAtlas(graphicsDevice, text);
545 Vector2 currentPos = position;
546 for (
int i = 0; i < text.Length; i++)
550 currentPos.X = position.X;
555 uint charIndex = text[i];
558 if (gd.TexIndex >= 0)
560 float halfCharHeight = gd.TexCoords.Height * 0.5f;
561 float slantStrength = 0.35f;
562 float topItalicOffset = italics ? ((halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
563 float bottomItalicOffset = italics ? ((-halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
565 Texture2D tex = textures[gd.TexIndex];
566 quadVertices[0].Position =
new Vector3(currentPos + gd.DrawOffset + (bottomItalicOffset, gd.TexCoords.Height), 0.0f);
567 quadVertices[0].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (
float)gd.TexCoords.Bottom / tex.Height);
568 quadVertices[0].Color = color;
570 quadVertices[1].Position =
new Vector3(currentPos + gd.DrawOffset + (topItalicOffset, 0.0f), 0.0f);
571 quadVertices[1].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (
float)gd.TexCoords.Top / tex.Height);
572 quadVertices[1].Color = color;
574 quadVertices[2].Position =
new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + bottomItalicOffset, gd.TexCoords.Height), 0.0f);
575 quadVertices[2].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (
float)gd.TexCoords.Bottom / tex.Height);
576 quadVertices[2].Color = color;
578 quadVertices[3].Position =
new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + topItalicOffset, 0.0f), 0.0f);
579 quadVertices[3].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (
float)gd.TexCoords.Top / tex.Height);
580 quadVertices[3].Color = color;
582 sb.Draw(tex, quadVertices, 0.0f);
584 currentPos.X += gd.Advance;
588 public void DrawStringWithColors(SpriteBatch sb,
string text, Vector2 position, Color color,
float rotation, Vector2 origin,
float scale, SpriteEffects se,
float layerDepth, in ImmutableArray<RichTextData>? richTextData,
int rtdOffset = 0, Alignment alignment = Alignment.TopLeft,
ForceUpperCase forceUpperCase =
Barotrauma.ForceUpperCase.Inherit)
590 DrawStringWithColors(sb, text, position, color, rotation, origin,
new Vector2(scale), se, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
593 public void DrawStringWithColors(SpriteBatch sb,
string text, Vector2 position, Color color,
float rotation, Vector2 origin, Vector2 scale, SpriteEffects se,
float layerDepth, in ImmutableArray<RichTextData>? richTextData,
int rtdOffset = 0, Alignment alignment = Alignment.TopLeft,
ForceUpperCase forceUpperCase =
Barotrauma.ForceUpperCase.Inherit)
596 if (!richTextData.HasValue || richTextData.Value.Length <= 0) {
DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth, forceUpperCase: forceUpperCase);
return; }
598 text = ApplyUpperCase(text, forceUpperCase);
600 float lineWidth = -1.0f;
601 Vector2 currentLineOffset = Vector2.Zero;
604 DynamicRenderAtlas(graphicsDevice, text);
608 Vector2 currentPos = position;
609 Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX :
new Vector2((
float)Math.Cos(rotation), (
float)Math.Sin(rotation));
611 int richTextDataIndex = 0;
612 RichTextData currentRichTextData = richTextData.Value[richTextDataIndex];
614 for (
int i = 0; i < text.Length; i++)
616 HandleNewLineAndAlignment(text, advanceUnit, position, scale, alignment, i,
617 ref lineWidth, ref currentLineOffset, ref lineNum, ref currentPos,
618 out uint charIndex, out
bool shouldContinue);
619 if (shouldContinue) {
continue; }
621 Color currentTextColor;
623 while (currentRichTextData !=
null && i + rtdOffset > currentRichTextData.EndIndex + lineNum)
626 currentRichTextData = richTextDataIndex < richTextData.Value.Length ? richTextData.Value[richTextDataIndex] :
null;
629 if (currentRichTextData !=
null && currentRichTextData.
StartIndex + lineNum <= i + rtdOffset && i + rtdOffset <= currentRichTextData.EndIndex + lineNum)
631 currentTextColor = currentRichTextData.
Color * currentRichTextData.
Alpha ?? color;
632 if (!
string.IsNullOrEmpty(currentRichTextData.
Metadata))
634 currentTextColor = Color.Lerp(currentTextColor, Color.White, 0.5f);
639 currentTextColor = color;
643 if (gd.TexIndex >= 0)
645 Texture2D tex = textures[gd.TexIndex];
647 drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
648 drawOffset.Y = gd.DrawOffset.X * advanceUnit.Y * scale.Y + gd.DrawOffset.Y * advanceUnit.X * scale.X;
650 sb.Draw(tex, currentPos + currentLineOffset + drawOffset, gd.TexCoords, currentTextColor, rotation, origin, scale, se, layerDepth);
652 currentPos += gd.Advance * advanceUnit * scale.X;
657 =>
WrapText(text, width, requestCharPos: 0, out _, returnAllCharPositions:
false, out _);
659 public string WrapText(
string text,
float width,
int requestCharPos, out Vector2 requestedCharPos)
660 =>
WrapText(text, width, requestCharPos, out requestedCharPos, returnAllCharPositions:
false, out _);
662 public string WrapText(
string text,
float width, out Vector2[] allCharPositions)
663 =>
WrapText(text, width, requestCharPos: 0, out _, returnAllCharPositions:
true, out allCharPositions);
670 private string WrapText(
string text,
673 out Vector2 requestedCharPos,
674 bool returnAllCharPositions,
675 out Vector2[] allCharPositions)
677 int currLineStart = 0;
678 Vector2 currentPos = Vector2.Zero;
679 Vector2 foundCharPos = Vector2.Zero;
680 int? lastBreakerIndex =
null;
682 var allCharPos = returnAllCharPositions ?
new Vector2[text.Length+1] :
null;
683 for (
int i = 0; i < text.Length; i++)
686 void recordCurrentPos()
688 if (i == requestCharPos) { foundCharPos = currentPos; }
690 if (allCharPos !=
null) { allCharPos[i] = currentPos; }
697 result += text[currLineStart..i].Remove(
"\n") +
"\n";
698 lastBreakerIndex =
null;
713 float advance = gd.Advance;
714 if (currentPos.X + advance >= width)
718 if (i > 0 &&
char.IsWhiteSpace(text[i]) && !
char.IsWhiteSpace(text[i - 1]))
722 advance = width - currentPos.X;
726 if (lastBreakerIndex.HasValue)
730 i = lastBreakerIndex.Value + 1;
731 gd = GetGlyphData(text[i]);
732 advance = gd.Advance;
739 currentPos.X += advance;
741 if (
char.IsWhiteSpace(text[i]) || TextManager.IsCJK($
"{text[i]}"))
743 lastBreakerIndex = i;
746 if (requestCharPos >= text.Length) { foundCharPos = currentPos; }
747 if (allCharPos !=
null) { allCharPos[text.Length] = currentPos; }
748 allCharPositions = allCharPos;
749 result += text[currLineStart..].Remove(
"\n");
750 requestedCharPos = foundCharPos;
766 float currentLineX = 0.0f;
767 Vector2 retVal = Vector2.Zero;
769 if (!removeExtraSpacing)
775 retVal.Y = baseHeight;
779 DynamicRenderAtlas(graphicsDevice, text);
782 for (
int i = 0; i < text.Length; i++)
790 uint charIndex = text[i];
793 currentLineX += gd.Advance;
794 retVal.X = Math.Max(retVal.X, currentLineX);
801 Vector2 retVal = Vector2.Zero;
804 var (gd, _) = GetGlyphDataAndTextureForChar(c);
805 retVal.X = gd.Advance;
813 DynamicRenderAtlas(graphicsDevice, c);
817 var tex = gd.TexIndex >= 0 ? textures[gd.TexIndex] :
null;
823 FontList.Remove(
this);
824 foreach (Texture2D texture
in textures)
Vector2 MeasureChar(char c)
void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, in ImmutableArray< RichTextData >? richTextData, int rtdOffset=0, Alignment alignment=Alignment.TopLeft, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit)
string WrapText(string text, float width)
string WrapText(string text, float width, out Vector2[] allCharPositions)
void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, Alignment alignment=Alignment.TopLeft, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit)
static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
string WrapText(string text, float width, int requestCharPos, out Vector2 requestedCharPos)
void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit, bool italics=false)
ScalableFont(string filename, uint size, GraphicsDevice gd=null, bool dynamicLoading=false, TextManager.SpeciallyHandledCharCategory speciallyHandledCharCategory=TextManager.SpeciallyHandledCharCategory.None)
Vector2 MeasureString(string text, bool removeExtraSpacing=false)
void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, in ImmutableArray< RichTextData >? richTextData, int rtdOffset=0, Alignment alignment=Alignment.TopLeft, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit)
TextManager.SpeciallyHandledCharCategory SpeciallyHandledCharCategory
void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment=Alignment.TopLeft, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit)
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
ScalableFont(ContentXElement element, uint defaultSize=14, GraphicsDevice gd=null)