|
@@ -0,0 +1,317 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2006-2014 LOVE Development Team
|
|
|
+ *
|
|
|
+ * This software is provided 'as-is', without any express or implied
|
|
|
+ * warranty. In no event will the authors be held liable for any damages
|
|
|
+ * arising from the use of this software.
|
|
|
+ *
|
|
|
+ * Permission is granted to anyone to use this software for any purpose,
|
|
|
+ * including commercial applications, and to alter it and redistribute it
|
|
|
+ * freely, subject to the following restrictions:
|
|
|
+ *
|
|
|
+ * 1. The origin of this software must not be misrepresented; you must not
|
|
|
+ * claim that you wrote the original software. If you use this software
|
|
|
+ * in a product, an acknowledgment in the product documentation would be
|
|
|
+ * appreciated but is not required.
|
|
|
+ * 2. Altered source versions must be plainly marked as such, and must not be
|
|
|
+ * misrepresented as being the original software.
|
|
|
+ * 3. This notice may not be removed or altered from any source distribution.
|
|
|
+ **/
|
|
|
+
|
|
|
+// LOVE
|
|
|
+#include "BMFontRasterizer.h"
|
|
|
+#include "filesystem/physfs/Filesystem.h"
|
|
|
+#include "image/Image.h"
|
|
|
+
|
|
|
+// C++
|
|
|
+#include <sstream>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+// C
|
|
|
+#include <cstdlib>
|
|
|
+#include <cstring>
|
|
|
+
|
|
|
+namespace love
|
|
|
+{
|
|
|
+namespace font
|
|
|
+{
|
|
|
+
|
|
|
+namespace
|
|
|
+{
|
|
|
+
|
|
|
+/**
|
|
|
+ * Helper class for parsing lines in BMFont definition files.
|
|
|
+ * NOTE: Does not properly handle multi-value attributes (e.g. 'padding' or
|
|
|
+ * 'spacing'.)
|
|
|
+ **/
|
|
|
+class BMFontLine
|
|
|
+{
|
|
|
+public:
|
|
|
+
|
|
|
+ BMFontLine(const std::string &line);
|
|
|
+
|
|
|
+ const std::string &getTag() const { return tag; }
|
|
|
+
|
|
|
+ int getAttributeInt(const char *name) const;
|
|
|
+ std::string getAttributeString(const char *name) const;
|
|
|
+
|
|
|
+private:
|
|
|
+
|
|
|
+ std::string tag;
|
|
|
+ std::map<std::string, std::string> attributes;
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+// This is not entirely robust...
|
|
|
+BMFontLine::BMFontLine(const std::string &line)
|
|
|
+{
|
|
|
+ // The tag name should always be at the start of the line.
|
|
|
+ tag = line.substr(0, line.find(' '));
|
|
|
+
|
|
|
+ size_t startpos = 0;
|
|
|
+
|
|
|
+ while (startpos < line.length())
|
|
|
+ {
|
|
|
+ // Find the next '=', which indicates a key-value pair.
|
|
|
+ size_t fpos = line.find('=', startpos);
|
|
|
+ if (fpos == std::string::npos || fpos + 1 >= line.length())
|
|
|
+ break;
|
|
|
+
|
|
|
+ // The key should be between a space character and the '='.
|
|
|
+ size_t keystart = line.rfind(' ', fpos);
|
|
|
+ if (keystart == std::string::npos)
|
|
|
+ break;
|
|
|
+
|
|
|
+ keystart++;
|
|
|
+
|
|
|
+ std::string key = line.substr(keystart, fpos - keystart);
|
|
|
+
|
|
|
+ size_t valstart = fpos + 1;
|
|
|
+ size_t valend = valstart + 1;
|
|
|
+
|
|
|
+ if (line[valstart] == '"')
|
|
|
+ {
|
|
|
+ // Values can be surrounded by quotes (a literal string.)
|
|
|
+ valstart++;
|
|
|
+ valend = line.find('"', valstart) - 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Otherwise look for the next space character after the '='.
|
|
|
+ valend = line.find(' ', valstart + 1) - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ valend = std::min(valend, line.length() - 1);
|
|
|
+
|
|
|
+ attributes[key] = line.substr(valstart, valend - valstart + 1);
|
|
|
+
|
|
|
+ startpos = valend + 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int BMFontLine::getAttributeInt(const char *name) const
|
|
|
+{
|
|
|
+ auto it = attributes.find(name);
|
|
|
+ if (it == attributes.end())
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return (int) strtol(it->second.c_str(), nullptr, 10);
|
|
|
+}
|
|
|
+
|
|
|
+std::string BMFontLine::getAttributeString(const char *name) const
|
|
|
+{
|
|
|
+ auto it = attributes.find(name);
|
|
|
+ if (it == attributes.end())
|
|
|
+ return "";
|
|
|
+
|
|
|
+ return it->second;
|
|
|
+}
|
|
|
+
|
|
|
+} // anonymous namespace
|
|
|
+
|
|
|
+
|
|
|
+BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist)
|
|
|
+ : fontSize(0)
|
|
|
+ , unicode(false)
|
|
|
+ , lineHeight(0)
|
|
|
+{
|
|
|
+ const std::string &filename = fontdef->getFilename();
|
|
|
+
|
|
|
+ size_t separatorpos = filename.rfind('/');
|
|
|
+ if (separatorpos != std::string::npos)
|
|
|
+ fontFolder = filename.substr(0, separatorpos);
|
|
|
+
|
|
|
+ // The parseConfig function will try to load any missing page images.
|
|
|
+ for (size_t i = 0; i < imagelist.size(); i++)
|
|
|
+ images[i] = imagelist[i];
|
|
|
+
|
|
|
+ std::string configtext((const char *) fontdef->getData(), fontdef->getSize());
|
|
|
+
|
|
|
+ parseConfig(configtext);
|
|
|
+}
|
|
|
+
|
|
|
+BMFontRasterizer::~BMFontRasterizer()
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+void BMFontRasterizer::parseConfig(const std::string &configtext)
|
|
|
+{
|
|
|
+ std::stringstream ss(configtext);
|
|
|
+ std::string line;
|
|
|
+
|
|
|
+ while (std::getline(ss, line))
|
|
|
+ {
|
|
|
+ BMFontLine cline(line);
|
|
|
+
|
|
|
+ const std::string &tag = cline.getTag();
|
|
|
+
|
|
|
+ if (tag == "info")
|
|
|
+ {
|
|
|
+ fontSize = cline.getAttributeInt("size");
|
|
|
+ unicode = cline.getAttributeInt("unicode") > 0;
|
|
|
+ }
|
|
|
+ else if (tag == "common")
|
|
|
+ {
|
|
|
+ lineHeight = cline.getAttributeInt("lineHeight");
|
|
|
+ metrics.ascent = cline.getAttributeInt("base");
|
|
|
+ metrics.height = lineHeight;
|
|
|
+ }
|
|
|
+ else if (tag == "page")
|
|
|
+ {
|
|
|
+ size_t pageindex = (size_t) cline.getAttributeInt("id");
|
|
|
+ std::string filename = cline.getAttributeString("file");
|
|
|
+
|
|
|
+ // The file name is relative to the font file's folder.
|
|
|
+ if (!fontFolder.empty())
|
|
|
+ filename = fontFolder + "/" + filename;
|
|
|
+
|
|
|
+ // Load the page file from disk into an ImageData, if necessary.
|
|
|
+ if (images[pageindex].get() == nullptr)
|
|
|
+ {
|
|
|
+ using namespace love::filesystem::physfs;
|
|
|
+
|
|
|
+ Filesystem *filesystem = Module::getInstance<Filesystem>(Module::M_FILESYSTEM);
|
|
|
+ image::Image *imagemodule = Module::getInstance<image::Image>(Module::M_IMAGE);
|
|
|
+
|
|
|
+ if (!filesystem)
|
|
|
+ throw love::Exception("Filesystem module not loaded!");
|
|
|
+ if (!imagemodule)
|
|
|
+ throw love::Exception("Image module not loaded!");
|
|
|
+
|
|
|
+ // Release these variables right away since StrongRef retains.
|
|
|
+ StrongRef<filesystem::FileData> data = filesystem->read(filename.c_str());
|
|
|
+ data->release();
|
|
|
+
|
|
|
+ images[pageindex].set(imagemodule->newImageData(data.get()));
|
|
|
+ images[pageindex]->release();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (tag == "char")
|
|
|
+ {
|
|
|
+ BMFontCharacter c = {};
|
|
|
+
|
|
|
+ uint32 id = (uint32) cline.getAttributeInt("id");
|
|
|
+
|
|
|
+ c.x = cline.getAttributeInt("x");
|
|
|
+ c.y = cline.getAttributeInt("y");
|
|
|
+ c.page = cline.getAttributeInt("page");
|
|
|
+
|
|
|
+ c.metrics.width = cline.getAttributeInt("width");
|
|
|
+ c.metrics.height = cline.getAttributeInt("height");
|
|
|
+ c.metrics.bearingX = cline.getAttributeInt("xoffset");
|
|
|
+ c.metrics.bearingY = -cline.getAttributeInt("yoffset");
|
|
|
+ c.metrics.advance = cline.getAttributeInt("xadvance");
|
|
|
+
|
|
|
+ characters[id] = c;
|
|
|
+ }
|
|
|
+ else if (tag == "kerning")
|
|
|
+ {
|
|
|
+ // TODO
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (lineHeight == 0)
|
|
|
+ throw love::Exception("Invalid BMFont file (missing line height attribute?)");
|
|
|
+
|
|
|
+ if (characters.size() == 0)
|
|
|
+ throw love::Exception("Invalid BMFont file (no character definitions?)");
|
|
|
+
|
|
|
+ // Verify the glyph character attributes.
|
|
|
+ for (const auto &cpair : characters)
|
|
|
+ {
|
|
|
+ const BMFontCharacter &c = cpair.second;
|
|
|
+
|
|
|
+ if (!unicode && cpair.first > 127)
|
|
|
+ throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)");
|
|
|
+
|
|
|
+ if (c.page < 0 || images[c.page].get() == nullptr)
|
|
|
+ throw love::Exception("Invalid BMFont character page id: %d", c.page);
|
|
|
+
|
|
|
+ const image::ImageData *id = images[c.page].get();
|
|
|
+
|
|
|
+ if (!id->inside(c.x, c.y) || !id->inside(c.x + c.metrics.width, c.y + c.metrics.height))
|
|
|
+ throw love::Exception("Invalid BMFont character coordinates.");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int BMFontRasterizer::getLineHeight() const
|
|
|
+{
|
|
|
+ return lineHeight;
|
|
|
+}
|
|
|
+
|
|
|
+GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const
|
|
|
+{
|
|
|
+ auto it = characters.find(glyph);
|
|
|
+
|
|
|
+ // Return an empty GlyphData if we don't have the glyph character.
|
|
|
+ if (it == characters.end())
|
|
|
+ return new GlyphData(glyph, GlyphMetrics(), GlyphData::FORMAT_RGBA);
|
|
|
+
|
|
|
+ const BMFontCharacter &c = it->second;
|
|
|
+ GlyphData *g = new GlyphData(glyph, c.metrics, GlyphData::FORMAT_RGBA);
|
|
|
+
|
|
|
+ const auto &imagepair = images.find(c.page);
|
|
|
+
|
|
|
+ if (imagepair == images.end())
|
|
|
+ {
|
|
|
+ g->release();
|
|
|
+ return new GlyphData(glyph, GlyphMetrics(), GlyphData::FORMAT_RGBA);
|
|
|
+ }
|
|
|
+
|
|
|
+ image::ImageData *imagedata = imagepair->second.get();
|
|
|
+ image::pixel *pixels = (image::pixel *) g->getData();
|
|
|
+ const image::pixel *ipixels = (const image::pixel *) imagedata->getData();
|
|
|
+
|
|
|
+ love::thread::Lock lock(imagedata->getMutex());
|
|
|
+
|
|
|
+ // Copy the subsection of the texture from the ImageData to the GlyphData.
|
|
|
+ for (int y = 0; y < c.metrics.height; y++)
|
|
|
+ {
|
|
|
+ size_t idindex = (c.y + y) * imagedata->getWidth() + c.x;
|
|
|
+ memcpy(&pixels[y * c.metrics.width], &ipixels[idindex], sizeof(image::pixel) * c.metrics.width);
|
|
|
+ }
|
|
|
+
|
|
|
+ return g;
|
|
|
+}
|
|
|
+
|
|
|
+int BMFontRasterizer::getGlyphCount() const
|
|
|
+{
|
|
|
+ return (int) characters.size();
|
|
|
+}
|
|
|
+
|
|
|
+bool BMFontRasterizer::hasGlyph(uint32 glyph) const
|
|
|
+{
|
|
|
+ return characters.find(glyph) != characters.end();
|
|
|
+}
|
|
|
+
|
|
|
+bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef)
|
|
|
+{
|
|
|
+ const char *data = (const char *) fontdef->getData();
|
|
|
+
|
|
|
+ // Check if the "info" tag is at the start of the file. This is a truly
|
|
|
+ // crappy test. Is the tag even guaranteed to be at the start?
|
|
|
+ return fontdef->getSize() > 4 && memcmp(data, "info", 4) == 0;
|
|
|
+}
|
|
|
+
|
|
|
+} // font
|
|
|
+} // love
|