std::shared_ptr<ApocalypseFont> ApocalypseFont::loadFont(Framework &fw, tinyxml2::XMLElement *fontElement) { int spacewidth = 0; int height = 0; int width = 0; UString fileName; UString fontName; const char *attr = fontElement->Attribute("name"); if (!attr) { LogError("apocfont element with no \"name\" attribute"); return nullptr; } fontName = attr; attr = fontElement->Attribute("path"); if (!attr) { LogError("apocfont \"%s\" with no \"path\" attribute", fontName.c_str()); return nullptr; } fileName = attr; auto err = fontElement->QueryIntAttribute("height", &height); if (err != tinyxml2::XML_NO_ERROR || height <= 0) { LogError("apocfont \"%s\" with invalid \"height\" attribute", fontName.c_str()); return nullptr; } err = fontElement->QueryIntAttribute("width", &width); if (err != tinyxml2::XML_NO_ERROR || width <= 0) { LogError("apocfont \"%s\" with invalid \"width\" attribute", fontName.c_str()); return nullptr; } err = fontElement->QueryIntAttribute("spacewidth", &spacewidth); if (err != tinyxml2::XML_NO_ERROR || spacewidth <= 0) { LogError("apocfont \"%s\" with invalid \"spacewidth\" attribute", fontName.c_str()); return nullptr; } auto file = fw.data->load_file(fileName); if (!file) { LogError("apocfont \"%s\" - Failed to open font path \"%s\"", fontName.c_str(), fileName.c_str()); return nullptr; } auto fileSize = file.size(); int glyphSize = height * width; int glyphCount = fileSize / glyphSize; if (!glyphCount) { LogError("apocfont \"%s\" - file \"%s\" contains no glyphs", fontName.c_str(), fileName.c_str()); } std::shared_ptr<ApocalypseFont> font(new ApocalypseFont); font->name = fontName; font->spacewidth = spacewidth; font->fontheight = height; font->averagecharacterwidth = 0; font->palette = fw.data->load_palette(fontElement->Attribute("palette")); for (auto *glyphNode = fontElement->FirstChildElement(); glyphNode; glyphNode = glyphNode->NextSiblingElement()) { UString nodeName = glyphNode->Name(); if (nodeName != "glyph") { LogError("apocfont \"%s\" has unexpected child node \"%s\", skipping", fontName.c_str(), nodeName.c_str()); continue; } int offset; err = glyphNode->QueryIntAttribute("offset", &offset); if (err != tinyxml2::XML_NO_ERROR) { LogError( "apocfont \"%s\" has glyph with invalid/missing offset attribute - skipping glyph", fontName.c_str()); continue; } attr = glyphNode->Attribute("string"); if (!attr) { LogError("apocfont \"%s\" has glyph with missing string attribute - skipping glyph", fontName.c_str()); continue; } UString glyphString(attr); if (glyphString.length() != 1) { LogError("apocfont \"%s\" glyph w/offset %d has %d codepoints, expected one - skipping " "glyph", fontName.c_str(), offset, glyphString.length()); continue; } if (offset >= glyphCount) { LogError("apocfont \"%s\" glyph \"%s\" has invalid offset %d - file contains a max of " "%d - skipping glyph", fontName.c_str(), glyphString.c_str(), offset, glyphCount); continue; } UniChar c = glyphString[0]; if (font->fontbitmaps.find(c) != font->fontbitmaps.end()) { LogError( "apocfont \"%s\" glyph \"%s\" has multiple definitions - skipping re-definition", fontName.c_str(), glyphString.c_str()); continue; } file.seekg(glyphSize * offset, std::ios::beg); int glyphWidth = 0; auto glyphImage = std::make_shared<PaletteImage>(Vec2<int>(width, height)); { PaletteImageLock imgLock(glyphImage, ImageLockUse::Write); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { uint8_t idx; file.read(reinterpret_cast<char *>(&idx), 1); imgLock.set(Vec2<int>{x, y}, idx); if (idx != 0 && glyphWidth < x) glyphWidth = x; } } } auto trimmedGlyph = std::make_shared<PaletteImage>(Vec2<int>{glyphWidth + 2, height}); PaletteImage::blit(glyphImage, Vec2<int>{0, 0}, trimmedGlyph); font->fontbitmaps[c] = trimmedGlyph; font->averagecharacterwidth += glyphWidth + 2; } // FIXME: Bit of a hack to handle spaces? auto spaceImage = std::make_shared<PaletteImage>(Vec2<int>{spacewidth, height}); // Defaults to transparent (0) font->fontbitmaps[UString::u8Char(' ')] = spaceImage; font->averagecharacterwidth /= font->fontbitmaps.size(); return font; }
sp<BitmapFont> BitmapFont::loadFont(const std::map<UniChar, UString> &glyphMap, int spaceWidth, int fontHeight, UString fontName, sp<Palette> defaultPalette) { auto font = mksp<BitmapFont>(); font->spacewidth = spaceWidth; font->fontheight = fontHeight; font->averagecharacterwidth = 0; font->name = fontName; font->palette = defaultPalette; size_t totalGlyphWidth = 0; for (auto &p : glyphMap) { auto fontImage = fw().data->loadImage(p.second); if (!fontImage) { LogError("Failed to read glyph image \"%s\"", p.second); continue; } auto paletteImage = std::dynamic_pointer_cast<PaletteImage>(fontImage); if (!paletteImage) { LogError("Glyph image \"%s\" doesn't look like a PaletteImage", p.second); continue; } unsigned int maxWidth = 0; // FIXME: Proper kerning // First find the widest non-transparent part of the glyph { PaletteImageLock imgLock(paletteImage, ImageLockUse::Read); for (unsigned int y = 0; y < paletteImage->size.y; y++) { for (unsigned int x = 0; x < paletteImage->size.x; x++) { if (imgLock.get({x, y}) != 0) { if (x > maxWidth) maxWidth = x; } } } } // Trim the glyph to the max non-transparent width + 2 px auto trimmedGlyph = mksp<PaletteImage>(Vec2<int>{maxWidth + 2, fontHeight}); PaletteImage::blit(paletteImage, trimmedGlyph); font->fontbitmaps[p.first] = trimmedGlyph; totalGlyphWidth += trimmedGlyph->size.x; } font->averagecharacterwidth = totalGlyphWidth / font->fontbitmaps.size(); // Always add a 'space' image: auto spaceImage = mksp<PaletteImage>(Vec2<int>{spaceWidth, fontHeight}); font->fontbitmaps[UString::u8Char(' ')] = spaceImage; return font; }