BitmapFontFileReader.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Globalization;
  7. using System.IO;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Xml;
  11. namespace MonoGame.Extended.Content.BitmapFonts;
  12. /// <summary>
  13. /// A utility class for reading the contents of a font file in the AngleCode BMFont file spec.
  14. /// </summary>
  15. public static class BitmapFontFileReader
  16. {
  17. /// <summary>
  18. /// Reads the content of the font file at the path specified.
  19. /// </summary>
  20. /// <param name="path">The path to the font file to read.</param>
  21. /// <returns>A <see cref="BitmapFontFileContent"/> instance containing the results of the read operation.</returns>
  22. /// <exception cref="InvalidOperationException">
  23. /// Thrown if the header for the file contents does not match a known header format.
  24. /// </exception>
  25. public static BitmapFontFileContent Read(string path)
  26. {
  27. using var stream = File.OpenRead(path);
  28. return Read(stream, path);
  29. }
  30. /// <summary>
  31. /// Reads the content of the font file at the path specified.
  32. /// </summary>
  33. /// <param name="stream">A <see cref="Stream"/> containing the font file contents to read.</param>
  34. /// <returns>A <see cref="BitmapFontFileContent"/> instance containing the results of the read operation.</returns>
  35. /// <exception cref="InvalidOperationException">
  36. /// Thrown if the header for the file contents does not match a known header format.
  37. /// </exception>
  38. [Obsolete("Use the overload that takes an explicit name parameter.")]
  39. public static BitmapFontFileContent Read(FileStream stream)
  40. {
  41. return Read(stream, stream.Name);
  42. }
  43. /// <summary>
  44. /// Reads the content of the font file at the path specified.
  45. /// </summary>
  46. /// <param name="stream">A <see cref="Stream"/> containing the font file contents to read.</param>
  47. /// <param name="name">The name or path that uniquely identifies this <see cref="BitmapFontFileContent"/>.</param>
  48. /// <returns>A <see cref="BitmapFontFileContent"/> instance containing the results of the read operation.</returns>
  49. /// <exception cref="InvalidOperationException">
  50. /// Thrown if the header for the file contents does not match a known header format.
  51. /// </exception>
  52. public static BitmapFontFileContent Read(Stream stream, string name)
  53. {
  54. long position = stream.Position;
  55. // Issue: MonoGame.Extended won't load XML format .fnt files if they begin with the byte order mark.
  56. // https://github.com/MonoGame-Extended/Monogame-Extended/issues/1073
  57. // It's possible that a consumer might edit the BMFont file using a different library such as SharpFNT.BitmapFont
  58. // which could save it with UTF-8 Byte Order Mark (BOM) preamble at the start of the file.
  59. // To get around this, we need to detect if the preamble is there and advance the stream past it if so.
  60. Span<byte> buffer = stackalloc byte[3];
  61. int bytesRead = stream.Read(buffer);
  62. if (bytesRead < 1)
  63. {
  64. throw new InvalidOperationException("Stream is empty or unreadable");
  65. }
  66. int sig;
  67. if (buffer.SequenceEqual(Encoding.UTF8.GetPreamble()))
  68. {
  69. sig = stream.ReadByte();
  70. stream.Seek(-1, SeekOrigin.Current);
  71. }
  72. else
  73. {
  74. sig = buffer[0];
  75. stream.Position = position;
  76. }
  77. var bmfFile = sig switch
  78. {
  79. // Binary header begins with [66, 77, 70, 3]
  80. 66 => ReadBinary(stream),
  81. // XML format begins with [60, 63, 120, 109]
  82. 60 => ReadXml(stream),
  83. // Text format begins with [105, 110, 102, 111]
  84. 105 => ReadText(stream),
  85. // Unknown format
  86. _ => throw new InvalidOperationException("This does not appear to be a valid BMFont file!")
  87. };
  88. bmfFile.Path = name;
  89. return bmfFile;
  90. }
  91. #region ------------------------ Read BMFFont Binary Formatted File -----------------------------------------------
  92. private static BitmapFontFileContent ReadBinary(Stream stream)
  93. {
  94. using BinaryReader reader = new BinaryReader(stream);
  95. return ReadBinary(reader);
  96. }
  97. private static BitmapFontFileContent ReadBinary(BinaryReader reader)
  98. {
  99. BitmapFontFileContent bmfFile = new BitmapFontFileContent();
  100. bmfFile.Header = AsType<BitmapFontFileContent.HeaderBlock>(reader.ReadBytes(BitmapFontFileContent.HeaderBlock.StructSize));
  101. if (!bmfFile.Header.IsValid)
  102. {
  103. throw new InvalidOperationException($"The BMFFont file header is invalid, this does not appear to be a valid BMFont file");
  104. }
  105. while (reader.BaseStream.Position < reader.BaseStream.Length)
  106. {
  107. byte blockType = reader.ReadByte();
  108. int blockSize = reader.ReadInt32();
  109. switch (blockType)
  110. {
  111. case 1:
  112. bmfFile.Info = AsType<BitmapFontFileContent.InfoBlock>(reader.ReadBytes(BitmapFontFileContent.InfoBlock.StructSize));
  113. int stringLen = blockSize - BitmapFontFileContent.InfoBlock.StructSize;
  114. bmfFile.FontName = Encoding.UTF8.GetString(reader.ReadBytes(stringLen)).Replace("\0", string.Empty);
  115. break;
  116. case 2:
  117. bmfFile.Common = AsType<BitmapFontFileContent.CommonBlock>(reader.ReadBytes(BitmapFontFileContent.CommonBlock.StructSize));
  118. break;
  119. case 3:
  120. string[] pages = Encoding.UTF8.GetString(reader.ReadBytes(blockSize)).Split('\0', StringSplitOptions.RemoveEmptyEntries);
  121. bmfFile.Pages.AddRange(pages);
  122. break;
  123. case 4:
  124. int characterCount = blockSize / BitmapFontFileContent.CharacterBlock.StructSize;
  125. for (int c = 0; c < characterCount; c++)
  126. {
  127. BitmapFontFileContent.CharacterBlock character = AsType<BitmapFontFileContent.CharacterBlock>(reader.ReadBytes(BitmapFontFileContent.CharacterBlock.StructSize));
  128. bmfFile.Characters.Add(character);
  129. }
  130. break;
  131. case 5:
  132. int kerningCount = blockSize / BitmapFontFileContent.KerningPairsBlock.StructSize;
  133. for (int k = 0; k < kerningCount; k++)
  134. {
  135. BitmapFontFileContent.KerningPairsBlock kerning = AsType<BitmapFontFileContent.KerningPairsBlock>(reader.ReadBytes(BitmapFontFileContent.KerningPairsBlock.StructSize));
  136. bmfFile.Kernings.Add(kerning);
  137. }
  138. break;
  139. default:
  140. reader.BaseStream.Seek(blockSize, SeekOrigin.Current);
  141. break;
  142. }
  143. }
  144. return bmfFile;
  145. }
  146. private static T AsType<T>(ReadOnlySpan<byte> buffer) where T : struct
  147. {
  148. T value;
  149. try
  150. {
  151. unsafe
  152. {
  153. fixed (byte* ptr = buffer)
  154. {
  155. value = Marshal.PtrToStructure<T>((IntPtr)ptr);
  156. }
  157. }
  158. return value;
  159. }
  160. catch
  161. {
  162. return default;
  163. }
  164. }
  165. #endregion --------------------- Read BMFFont Binary Formatted File -----------------------------------------------
  166. #region ------------------------ Read BMFFont Xml Formatted File --------------------------------------------------
  167. private static BitmapFontFileContent ReadXml(Stream stream)
  168. {
  169. BitmapFontFileContent bmfFile = new BitmapFontFileContent();
  170. // XML does not contain the header like binary so we manually create it
  171. bmfFile.Header = new() { B = (byte)'B', M = (byte)'M', F = (byte)'F', Version = 3 };
  172. var document = new XmlDocument();
  173. document.Load(stream);
  174. var root = document.DocumentElement;
  175. ReadInfoNode(bmfFile, root);
  176. ReadCommonNode(bmfFile, root);
  177. ReadPageNodes(bmfFile, root);
  178. ReadCharacterNodes(bmfFile, root);
  179. ReadKerningNodes(bmfFile, root);
  180. return bmfFile;
  181. }
  182. private static void ReadInfoNode(BitmapFontFileContent bmfFile, XmlNode root)
  183. {
  184. var node = root.SelectSingleNode("info");
  185. bmfFile.FontName = node.GetStringAttribute("face");
  186. bmfFile.Info.FontSize = node.GetInt16Attribute("size");
  187. var smooth = node.GetByteAttribute("smooth");
  188. var unicode = node.GetByteAttribute("unicode");
  189. var italic = node.GetByteAttribute("italic");
  190. var bold = node.GetByteAttribute("bold");
  191. var fixedHeight = node.GetByteAttribute("fixedHeight");
  192. bmfFile.Info.BitField = (byte)(smooth << 7 | unicode << 6 | italic << 5 | bold << 4 | fixedHeight << 3);
  193. bmfFile.Info.CharSet = node.GetByteAttribute("charSet");
  194. bmfFile.Info.StretchH = node.GetUInt16Attribute("stretchH");
  195. bmfFile.Info.AA = node.GetByteAttribute("aa");
  196. var paddingValues = node.GetByteDelimitedAttribute("padding", 4);
  197. bmfFile.Info.PaddingUp = paddingValues[0];
  198. bmfFile.Info.PaddingRight = paddingValues[1];
  199. bmfFile.Info.PaddingDown = paddingValues[2];
  200. bmfFile.Info.PaddingLeft = paddingValues[3];
  201. var spacingValues = node.GetSignedByteDelimitedAttribute("spacing", 2);
  202. bmfFile.Info.SpacingHoriz = spacingValues[0];
  203. bmfFile.Info.SpacingVert = spacingValues[1];
  204. bmfFile.Info.Outline = node.GetByteAttribute("outline");
  205. }
  206. private static void ReadCommonNode(BitmapFontFileContent bmfFile, XmlNode root)
  207. {
  208. var node = root.SelectSingleNode("common");
  209. bmfFile.Common.LineHeight = node.GetUInt16Attribute("lineHeight");
  210. bmfFile.Common.Base = node.GetUInt16Attribute("base");
  211. bmfFile.Common.ScaleW = node.GetUInt16Attribute("scaleW");
  212. bmfFile.Common.ScaleH = node.GetUInt16Attribute("scaleH");
  213. bmfFile.Common.Pages = node.GetUInt16Attribute("pages");
  214. var packed = node.GetByteAttribute("packed");
  215. bmfFile.Common.BitField = (byte)(packed << 7);
  216. bmfFile.Common.AlphaChnl = node.GetByteAttribute("alphaChnl");
  217. bmfFile.Common.RedChnl = node.GetByteAttribute("redChnl");
  218. bmfFile.Common.GreenChnl = node.GetByteAttribute("greenChnl");
  219. bmfFile.Common.BlueChnl = node.GetByteAttribute("blueChnl");
  220. }
  221. private static void ReadPageNodes(BitmapFontFileContent bmfFile, XmlNode root)
  222. {
  223. var nodes = root.SelectNodes("pages/page");
  224. foreach (XmlNode node in nodes)
  225. {
  226. string file = node.GetStringAttribute("file");
  227. bmfFile.Pages.Add(file);
  228. }
  229. }
  230. private static void ReadCharacterNodes(BitmapFontFileContent bmfFile, XmlNode root)
  231. {
  232. var nodes = root.SelectNodes("chars/char");
  233. foreach (XmlNode node in nodes)
  234. {
  235. var character = new BitmapFontFileContent.CharacterBlock
  236. {
  237. ID = node.GetInt32Attribute("id"),
  238. X = node.GetUInt16Attribute("x"),
  239. Y = node.GetUInt16Attribute("y"),
  240. Width = node.GetUInt16Attribute("width"),
  241. Height = node.GetUInt16Attribute("height"),
  242. XOffset = node.GetInt16Attribute("xoffset"),
  243. YOffset = node.GetInt16Attribute("yoffset"),
  244. XAdvance = node.GetInt16Attribute("xadvance"),
  245. Page = node.GetByteAttribute("page"),
  246. Chnl = node.GetByteAttribute("chnl"),
  247. };
  248. bmfFile.Characters.Add(character);
  249. }
  250. }
  251. private static void ReadKerningNodes(BitmapFontFileContent bmfFile, XmlNode root)
  252. {
  253. var nodes = root.SelectNodes("kernings/kerning");
  254. foreach (XmlNode node in nodes)
  255. {
  256. var kerning = new BitmapFontFileContent.KerningPairsBlock
  257. {
  258. First = node.GetUInt32Attribute("first"),
  259. Second = node.GetUInt32Attribute("second"),
  260. Amount = node.GetInt16Attribute("amount"),
  261. };
  262. bmfFile.Kernings.Add(kerning);
  263. }
  264. }
  265. #endregion --------------------- Read BMFFont Xml Formatted File --------------------------------------------------
  266. #region ------------------------ Read BMFFont Text Formatted File -------------------------------------------------
  267. private static BitmapFontFileContent ReadText(Stream stream)
  268. {
  269. var bmfFile = new BitmapFontFileContent();
  270. // Text does not contain the header like binary so we manually create it
  271. bmfFile.Header = new() { B = (byte)'B', M = (byte)'M', F = (byte)'F', Version = 3 };
  272. using var reader = new StreamReader(stream);
  273. string line = default;
  274. while ((line = reader.ReadLine()) != null)
  275. {
  276. var tokens = GetTokens(line);
  277. if (tokens.Count == 0)
  278. {
  279. continue;
  280. }
  281. switch (tokens[0])
  282. {
  283. case "info":
  284. ReadInfoTokens(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
  285. break;
  286. case "common":
  287. ReadCommonTokens(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
  288. break;
  289. case "page":
  290. ReadPageTokens(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
  291. break;
  292. case "char":
  293. ReadCharacterTokens(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
  294. break;
  295. case "kerning":
  296. ReadKerningTokens(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
  297. break;
  298. }
  299. }
  300. return bmfFile;
  301. }
  302. private static void ReadInfoTokens(BitmapFontFileContent bmfFile, ReadOnlySpan<string> tokens)
  303. {
  304. for (int i = 0; i < tokens.Length; ++i)
  305. {
  306. var split = tokens[i].Split('=');
  307. if (split.Length != 2)
  308. {
  309. continue;
  310. }
  311. switch (split[0])
  312. {
  313. case "face":
  314. bmfFile.FontName = split[1].Replace("\"", string.Empty);
  315. break;
  316. case "size":
  317. bmfFile.Info.FontSize = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
  318. break;
  319. case "smooth":
  320. var smooth = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  321. bmfFile.Info.BitField |= (byte)(smooth << 7);
  322. break;
  323. case "unicode":
  324. var unicode = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  325. bmfFile.Info.BitField |= (byte)(unicode << 6);
  326. break;
  327. case "italic":
  328. var italic = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  329. bmfFile.Info.BitField |= (byte)(italic << 5);
  330. break;
  331. case "bold":
  332. var bold = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  333. bmfFile.Info.BitField |= (byte)(bold << 4);
  334. break;
  335. case "fixedHeight":
  336. byte fixedHeight = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  337. bmfFile.Info.BitField |= (byte)(fixedHeight << 3);
  338. break;
  339. case "stretchH":
  340. bmfFile.Info.StretchH = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  341. break;
  342. case "aa":
  343. bmfFile.Info.AA = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  344. break;
  345. case "padding":
  346. var paddingValues = split[1].Split(',');
  347. if (paddingValues.Length == 4)
  348. {
  349. bmfFile.Info.PaddingUp = Convert.ToByte(paddingValues[0], CultureInfo.InvariantCulture);
  350. bmfFile.Info.PaddingRight = Convert.ToByte(paddingValues[1], CultureInfo.InvariantCulture);
  351. bmfFile.Info.PaddingDown = Convert.ToByte(paddingValues[2], CultureInfo.InvariantCulture);
  352. bmfFile.Info.PaddingLeft = Convert.ToByte(paddingValues[3], CultureInfo.InvariantCulture);
  353. }
  354. break;
  355. case "spacing":
  356. var spacingValues = split[1].Split(',');
  357. if (spacingValues.Length == 2)
  358. {
  359. bmfFile.Info.SpacingHoriz = Convert.ToSByte(spacingValues[0], CultureInfo.InvariantCulture);
  360. bmfFile.Info.SpacingVert = Convert.ToSByte(spacingValues[1], CultureInfo.InvariantCulture);
  361. }
  362. break;
  363. case "outline":
  364. bmfFile.Info.Outline = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  365. break;
  366. }
  367. }
  368. }
  369. private static void ReadCommonTokens(BitmapFontFileContent bmfFile, ReadOnlySpan<string> tokens)
  370. {
  371. for (int i = 0; i < tokens.Length; ++i)
  372. {
  373. var split = tokens[i].Split('=');
  374. if (split.Length != 2)
  375. {
  376. continue;
  377. }
  378. switch (split[0])
  379. {
  380. case "lineHeight":
  381. bmfFile.Common.LineHeight = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  382. break;
  383. case "base":
  384. bmfFile.Common.Base = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  385. break;
  386. case "scaleW":
  387. bmfFile.Common.ScaleW = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  388. break;
  389. case "scaleH":
  390. bmfFile.Common.ScaleH = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  391. break;
  392. case "pages":
  393. bmfFile.Common.Pages = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  394. break;
  395. case "packed":
  396. var packed = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  397. bmfFile.Common.BitField |= (byte)(packed << 7);
  398. break;
  399. case "alphaChnl":
  400. bmfFile.Common.AlphaChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  401. break;
  402. case "redChnl":
  403. bmfFile.Common.RedChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  404. break;
  405. case "greenChnl":
  406. bmfFile.Common.GreenChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  407. break;
  408. case "blueChnl":
  409. bmfFile.Common.BlueChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  410. break;
  411. }
  412. }
  413. }
  414. private static void ReadPageTokens(BitmapFontFileContent bmfFile, ReadOnlySpan<string> tokens)
  415. {
  416. for (var i = 0; i < tokens.Length; ++i)
  417. {
  418. var split = tokens[i].Split('=');
  419. if (split.Length != 2)
  420. {
  421. continue;
  422. }
  423. if (split[0] == "file")
  424. {
  425. var page = split[1].Replace("\"", string.Empty);
  426. bmfFile.Pages.Add(page);
  427. }
  428. }
  429. }
  430. private static void ReadCharacterTokens(BitmapFontFileContent bmfFile, ReadOnlySpan<string> tokens)
  431. {
  432. var character = default(BitmapFontFileContent.CharacterBlock);
  433. for (var i = 0; i < tokens.Length; ++i)
  434. {
  435. var split = tokens[i].Split('=');
  436. if (split.Length != 2)
  437. {
  438. continue;
  439. }
  440. switch (split[0])
  441. {
  442. case "id":
  443. character.ID = Convert.ToInt32(split[1], CultureInfo.InvariantCulture);
  444. break;
  445. case "x":
  446. character.X = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  447. break;
  448. case "y":
  449. character.Y = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  450. break;
  451. case "width":
  452. character.Width = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  453. break;
  454. case "height":
  455. character.Height = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
  456. break;
  457. case "xoffset":
  458. character.XOffset = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
  459. break;
  460. case "yoffset":
  461. character.YOffset = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
  462. break;
  463. case "xadvance":
  464. character.XAdvance = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
  465. break;
  466. case "page":
  467. character.Page = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  468. break;
  469. case "chnl":
  470. character.Chnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
  471. break;
  472. }
  473. }
  474. bmfFile.Characters.Add(character);
  475. }
  476. private static void ReadKerningTokens(BitmapFontFileContent bmfFile, ReadOnlySpan<string> tokens)
  477. {
  478. var kerning = default(BitmapFontFileContent.KerningPairsBlock);
  479. for (var i = 0; i < tokens.Length; ++i)
  480. {
  481. var split = tokens[i].Split('=');
  482. if (split.Length != 2)
  483. {
  484. continue;
  485. }
  486. switch (split[0])
  487. {
  488. case "first":
  489. kerning.First = Convert.ToUInt32(split[1], CultureInfo.InvariantCulture);
  490. break;
  491. case "second":
  492. kerning.Second = Convert.ToUInt32(split[1], CultureInfo.InvariantCulture);
  493. break;
  494. case "amount":
  495. kerning.Amount = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
  496. break;
  497. }
  498. }
  499. bmfFile.Kernings.Add(kerning);
  500. }
  501. private static List<string> GetTokens(ReadOnlySpan<char> line)
  502. {
  503. var tokens = new List<string>();
  504. var currentToken = new StringBuilder();
  505. var inQuotes = false;
  506. for (int i = 0; i < line.Length; i++)
  507. {
  508. var c = line[i];
  509. if (c == ' ' && !inQuotes)
  510. {
  511. if (currentToken.Length > 0)
  512. {
  513. tokens.Add(currentToken.ToString());
  514. currentToken.Clear();
  515. }
  516. }
  517. else if (c == '"')
  518. {
  519. inQuotes = !inQuotes;
  520. }
  521. else
  522. {
  523. currentToken.Append(c);
  524. }
  525. }
  526. if (currentToken.Length > 0)
  527. {
  528. tokens.Add(currentToken.ToString());
  529. }
  530. return tokens;
  531. }
  532. #endregion --------------------- Read BMFFont Text Formatted File -------------------------------------------------
  533. }