BMFontRasterizer.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * Copyright (c) 2006-2016 LOVE Development Team
  3. *
  4. * This software is provided 'as-is', without any express or implied
  5. * warranty. In no event will the authors be held liable for any damages
  6. * arising from the use of this software.
  7. *
  8. * Permission is granted to anyone to use this software for any purpose,
  9. * including commercial applications, and to alter it and redistribute it
  10. * freely, subject to the following restrictions:
  11. *
  12. * 1. The origin of this software must not be misrepresented; you must not
  13. * claim that you wrote the original software. If you use this software
  14. * in a product, an acknowledgment in the product documentation would be
  15. * appreciated but is not required.
  16. * 2. Altered source versions must be plainly marked as such, and must not be
  17. * misrepresented as being the original software.
  18. * 3. This notice may not be removed or altered from any source distribution.
  19. **/
  20. // LOVE
  21. #include "BMFontRasterizer.h"
  22. #include "filesystem/Filesystem.h"
  23. #include "image/Image.h"
  24. // C++
  25. #include <sstream>
  26. #include <vector>
  27. #include <algorithm>
  28. // C
  29. #include <cstdlib>
  30. #include <cstring>
  31. namespace love
  32. {
  33. namespace font
  34. {
  35. namespace
  36. {
  37. /**
  38. * Helper class for parsing lines in BMFont definition files.
  39. * NOTE: Does not properly handle multi-value attributes (e.g. 'padding' or
  40. * 'spacing'.)
  41. **/
  42. class BMFontLine
  43. {
  44. public:
  45. BMFontLine(const std::string &line);
  46. const std::string &getTag() const { return tag; }
  47. int getAttributeInt(const char *name) const;
  48. std::string getAttributeString(const char *name) const;
  49. private:
  50. std::string tag;
  51. std::unordered_map<std::string, std::string> attributes;
  52. };
  53. // This is not entirely robust...
  54. BMFontLine::BMFontLine(const std::string &line)
  55. {
  56. // The tag name should always be at the start of the line.
  57. tag = line.substr(0, line.find(' '));
  58. size_t startpos = 0;
  59. while (startpos < line.length())
  60. {
  61. // Find the next '=', which indicates a key-value pair.
  62. size_t fpos = line.find('=', startpos);
  63. if (fpos == std::string::npos || fpos + 1 >= line.length())
  64. break;
  65. // The key should be between a space character and the '='.
  66. size_t keystart = line.rfind(' ', fpos);
  67. if (keystart == std::string::npos)
  68. break;
  69. keystart++;
  70. std::string key = line.substr(keystart, fpos - keystart);
  71. size_t valstart = fpos + 1;
  72. size_t valend = valstart + 1;
  73. if (line[valstart] == '"')
  74. {
  75. // Values can be surrounded by quotes (a literal string.)
  76. valstart++;
  77. valend = line.find('"', valstart) - 1;
  78. }
  79. else
  80. {
  81. // Otherwise look for the next space character after the '='.
  82. valend = line.find(' ', valstart + 1) - 1;
  83. }
  84. valend = std::min(valend, line.length() - 1);
  85. attributes[key] = line.substr(valstart, valend - valstart + 1);
  86. startpos = valend + 1;
  87. }
  88. }
  89. int BMFontLine::getAttributeInt(const char *name) const
  90. {
  91. auto it = attributes.find(name);
  92. if (it == attributes.end())
  93. return 0;
  94. return (int) strtol(it->second.c_str(), nullptr, 10);
  95. }
  96. std::string BMFontLine::getAttributeString(const char *name) const
  97. {
  98. auto it = attributes.find(name);
  99. if (it == attributes.end())
  100. return "";
  101. return it->second;
  102. }
  103. } // anonymous namespace
  104. BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist, float pixeldensity)
  105. : fontSize(0)
  106. , unicode(false)
  107. , lineHeight(0)
  108. {
  109. this->pixelDensity = pixeldensity;
  110. const std::string &filename = fontdef->getFilename();
  111. size_t separatorpos = filename.rfind('/');
  112. if (separatorpos != std::string::npos)
  113. fontFolder = filename.substr(0, separatorpos);
  114. // The parseConfig function will try to load any missing page images.
  115. for (int i = 0; i < (int) imagelist.size(); i++)
  116. images[i] = imagelist[i];
  117. std::string configtext((const char *) fontdef->getData(), fontdef->getSize());
  118. parseConfig(configtext);
  119. }
  120. BMFontRasterizer::~BMFontRasterizer()
  121. {
  122. }
  123. void BMFontRasterizer::parseConfig(const std::string &configtext)
  124. {
  125. std::stringstream ss(configtext);
  126. std::string line;
  127. while (std::getline(ss, line))
  128. {
  129. BMFontLine cline(line);
  130. const std::string &tag = cline.getTag();
  131. if (tag == "info")
  132. {
  133. fontSize = cline.getAttributeInt("size");
  134. unicode = cline.getAttributeInt("unicode") > 0;
  135. }
  136. else if (tag == "common")
  137. {
  138. lineHeight = cline.getAttributeInt("lineHeight");
  139. metrics.ascent = cline.getAttributeInt("base");
  140. }
  141. else if (tag == "page")
  142. {
  143. int pageindex = cline.getAttributeInt("id");
  144. std::string filename = cline.getAttributeString("file");
  145. // The file name is relative to the font file's folder.
  146. if (!fontFolder.empty())
  147. filename = fontFolder + "/" + filename;
  148. // Load the page file from disk into an ImageData, if necessary.
  149. if (images[pageindex].get() == nullptr)
  150. {
  151. using namespace love::filesystem;
  152. using namespace love::image;
  153. auto filesystem = Module::getInstance<Filesystem>(Module::M_FILESYSTEM);
  154. auto imagemodule = Module::getInstance<image::Image>(Module::M_IMAGE);
  155. if (!filesystem)
  156. throw love::Exception("Filesystem module not loaded!");
  157. if (!imagemodule)
  158. throw love::Exception("Image module not loaded!");
  159. // read() returns a retained ref already.
  160. StrongRef<FileData> data(filesystem->read(filename.c_str()), Acquire::NORETAIN);
  161. ImageData *imagedata = imagemodule->newImageData(data.get());
  162. if (imagedata->getFormat() != PIXELFORMAT_RGBA8)
  163. {
  164. imagedata->release();
  165. throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
  166. }
  167. // Same with newImageData.
  168. images[pageindex].set(imagedata, Acquire::NORETAIN);
  169. }
  170. }
  171. else if (tag == "char")
  172. {
  173. BMFontCharacter c = {};
  174. uint32 id = (uint32) cline.getAttributeInt("id");
  175. c.x = cline.getAttributeInt("x");
  176. c.y = cline.getAttributeInt("y");
  177. c.page = cline.getAttributeInt("page");
  178. c.metrics.width = cline.getAttributeInt("width");
  179. c.metrics.height = cline.getAttributeInt("height");
  180. c.metrics.bearingX = cline.getAttributeInt("xoffset");
  181. c.metrics.bearingY = -cline.getAttributeInt("yoffset");
  182. c.metrics.advance = cline.getAttributeInt("xadvance");
  183. characters[id] = c;
  184. }
  185. else if (tag == "kerning")
  186. {
  187. uint32 firstid = (uint32) cline.getAttributeInt("first");
  188. uint32 secondid = (uint32) cline.getAttributeInt("second");
  189. uint64 packedids = ((uint64) firstid << 32) | (uint64) secondid;
  190. kerning[packedids] = cline.getAttributeInt("amount");
  191. }
  192. }
  193. if (characters.size() == 0)
  194. throw love::Exception("Invalid BMFont file (no character definitions?)");
  195. // Try to guess the line height if the lineheight attribute isn't found.
  196. bool guessheight = lineHeight == 0;
  197. // Verify the glyph character attributes.
  198. for (const auto &cpair : characters)
  199. {
  200. const BMFontCharacter &c = cpair.second;
  201. int width = c.metrics.width;
  202. int height = c.metrics.height;
  203. if (!unicode && cpair.first > 127)
  204. throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)");
  205. if (c.page < 0 || images[c.page].get() == nullptr)
  206. throw love::Exception("Invalid BMFont character page id: %d", c.page);
  207. const image::ImageData *id = images[c.page].get();
  208. if (!id->inside(c.x, c.y))
  209. throw love::Exception("Invalid coordinates for BMFont character %u.", cpair.first);
  210. if (width > 0 && !id->inside(c.x + width - 1, c.y))
  211. throw love::Exception("Invalid width %d for BMFont character %u.", width, cpair.first);
  212. if (height > 0 && !id->inside(c.x, c.y + height - 1))
  213. throw love::Exception("Invalid height %d for BMFont character %u.", height, cpair.first);
  214. if (guessheight)
  215. lineHeight = std::max(lineHeight, c.metrics.height);
  216. }
  217. metrics.height = lineHeight;
  218. }
  219. int BMFontRasterizer::getLineHeight() const
  220. {
  221. return lineHeight;
  222. }
  223. GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const
  224. {
  225. auto it = characters.find(glyph);
  226. // Return an empty GlyphData if we don't have the glyph character.
  227. if (it == characters.end())
  228. return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
  229. const BMFontCharacter &c = it->second;
  230. GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8);
  231. const auto &imagepair = images.find(c.page);
  232. if (imagepair == images.end())
  233. {
  234. g->release();
  235. return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
  236. }
  237. image::ImageData *imagedata = imagepair->second.get();
  238. size_t pixelsize = imagedata->getPixelSize();
  239. image::pixel *pixels = (image::pixel *) g->getData();
  240. const image::pixel *ipixels = (const image::pixel *) imagedata->getData();
  241. love::thread::Lock lock(imagedata->getMutex());
  242. // Copy the subsection of the texture from the ImageData to the GlyphData.
  243. for (int y = 0; y < c.metrics.height; y++)
  244. {
  245. size_t idindex = (c.y + y) * imagedata->getWidth() + c.x;
  246. memcpy(&pixels[y * c.metrics.width], &ipixels[idindex], pixelsize * c.metrics.width);
  247. }
  248. return g;
  249. }
  250. int BMFontRasterizer::getGlyphCount() const
  251. {
  252. return (int) characters.size();
  253. }
  254. bool BMFontRasterizer::hasGlyph(uint32 glyph) const
  255. {
  256. return characters.find(glyph) != characters.end();
  257. }
  258. float BMFontRasterizer::getKerning(uint32 leftglyph, uint32 rightglyph) const
  259. {
  260. uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph;
  261. auto it = kerning.find(packedglyphs);
  262. if (it != kerning.end())
  263. return it->second;
  264. return 0.0f;
  265. }
  266. Rasterizer::DataType BMFontRasterizer::getDataType() const
  267. {
  268. return DATA_IMAGE;
  269. }
  270. bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef)
  271. {
  272. const char *data = (const char *) fontdef->getData();
  273. // Check if the "info" tag is at the start of the file. This is a truly
  274. // crappy test. Is the tag even guaranteed to be at the start?
  275. return fontdef->getSize() > 4 && memcmp(data, "info", 4) == 0;
  276. }
  277. } // font
  278. } // love