Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
33 kB · 737 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738#include "fontloader.hpp"
#include <array>#include <format>#include <stdexcept>#include <string_view>
#include <osg/Image>
#include <osgDB/WriteFile>
#include <MyGUI_FactoryManager.h>#include <MyGUI_RenderManager.h>#include <MyGUI_ResourceManager.h>#include <MyGUI_ResourceManualFont.h>#include <MyGUI_ResourceTrueTypeFont.h>#include <MyGUI_XmlDocument.h>
#include <components/debug/debuglog.hpp>
#include <components/fallback/fallback.hpp>
#include <components/vfs/manager.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/myguiplatform/scalinglayer.hpp>
#include <components/settings/values.hpp>
namespace{ MyGUI::xml::ElementPtr getProperty(MyGUI::xml::ElementPtr resourceNode, std::string_view propertyName) { MyGUI::xml::ElementPtr propertyNode = nullptr; MyGUI::xml::ElementEnumerator propertyIterator = resourceNode->getElementEnumerator(); while (propertyIterator.next("Property")) { if (propertyIterator->findAttribute("key") == propertyName) { propertyNode = propertyIterator.current(); break; } }
return propertyNode; }
MyGUI::IntSize getBookSize(MyGUI::IDataStream* layersStream) { MyGUI::xml::Document xmlDocument; xmlDocument.open(layersStream); MyGUI::xml::ElementPtr root = xmlDocument.getRoot(); MyGUI::xml::ElementEnumerator layersIterator = root->getElementEnumerator(); while (layersIterator.next("Layer")) { if (layersIterator->findAttribute("name") == "JournalBooks") { MyGUI::xml::ElementPtr sizeProperty = getProperty(layersIterator.current(), "Size"); if (sizeProperty != nullptr) { auto sizeValue = sizeProperty->findAttribute("value"); if (!sizeValue.empty()) return MyGUI::IntSize::parse(sizeValue); } } }
return MyGUI::RenderManager::getInstance().getViewSize(); }
unsigned long utf8ToUnicode(std::string_view utf8) { if (utf8.empty()) return 0; size_t i = 0; unsigned long unicode; size_t numbytes; unsigned char ch = utf8[i++]; if (ch <= 0x7F) { unicode = ch; numbytes = 0; } else if (ch <= 0xBF) { throw std::logic_error("not a UTF-8 string"); } else if (ch <= 0xDF) { unicode = ch & 0x1F; numbytes = 1; } else if (ch <= 0xEF) { unicode = ch & 0x0F; numbytes = 2; } else if (ch <= 0xF7) { unicode = ch & 0x07; numbytes = 3; } else { throw std::logic_error("not a UTF-8 string"); } for (size_t j = 0; j < numbytes; ++j) { ch = utf8[i++]; if (ch < 0x80 || ch > 0xBF) throw std::logic_error("not a UTF-8 string"); unicode <<= 6; unicode += ch & 0x3F; } if (unicode >= 0xD800 && unicode <= 0xDFFF) throw std::logic_error("not a UTF-8 string"); if (unicode > 0x10FFFF) throw std::logic_error("not a UTF-8 string");
return unicode; }
/// This is a hack for Polish font unsigned char mapUtf8Char(unsigned char c) { switch (c) { case 0x80: return 0xc6; case 0x81: return 0x9c; case 0x82: return 0xe6; case 0x83: return 0xb3; case 0x84: return 0xf1; case 0x85: return 0xb9; case 0x86: return 0xbf; case 0x87: return 0x9f; case 0x88: return 0xea; case 0x89: return 0xea; case 0x8a: return 0x00; // not contained in win1250 case 0x8b: return 0x00; // not contained in win1250 case 0x8c: return 0x8f; case 0x8d: return 0xaf; case 0x8e: return 0xa5; case 0x8f: return 0x8c; case 0x90: return 0xca; case 0x93: return 0xa3; case 0x94: return 0xf6; case 0x95: return 0xf3; case 0x96: return 0xaf; case 0x97: return 0x8f; case 0x99: return 0xd3; case 0x9a: return 0xd1; case 0x9c: return 0x00; // not contained in win1250 case 0xa0: return 0xb9; case 0xa1: return 0xaf; case 0xa2: return 0xf3; case 0xa3: return 0xbf; case 0xa4: return 0x00; // not contained in win1250 case 0xe1: return 0x8c; case 0xe3: return 0x00; // not contained in win1250 case 0xf5: return 0x00; // not contained in win1250 default: return c; } }
// getUnicode includes various hacks for dealing with Morrowind's .fnt files that are *mostly* // in the expected win12XX encoding, but also have randomly swapped characters sometimes. // Looks like the Morrowind developers found standard encodings too boring and threw in some twists for fun. unsigned long getUnicode(unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) // Hack for polish font { const std::array<char, 2> str{ static_cast<char>(mapUtf8Char(c)), '\0' }; return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); } else { const std::array<char, 2> str{ static_cast<char>(c), '\0' }; return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); } }
[[noreturn]] void fail(std::istream& stream, VFS::Path::NormalizedView fileName, std::string_view message) { std::stringstream error; error << "Font loading error: " << message; error << "\n File: " << fileName; error << "\n Offset: 0x" << std::hex << stream.tellg(); throw std::runtime_error(error.str()); }}
namespace Gui{
FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor, bool exportFonts) : mVFS(vfs) , mScalingFactor(scalingFactor) , mExportFonts(exportFonts) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; else mEncoding = encoding;
MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = MyGUI::newDelegate(this, &FontLoader::overrideLineHeight);
loadFonts(); }
void FontLoader::loadFonts() { constexpr FontId defaultFontId{ "DefaultFont" }; constexpr FontId scrollFontId{ "ScrollFont" }; constexpr FontId monoFontId{ "MonoFont" };
const std::string_view defaultFont = Fallback::Map::getString("Fonts_Font_0"); const std::string_view scrollFont = Fallback::Map::getString("Fonts_Font_2");
loadFont(defaultFontId, defaultFont); loadFont(scrollFontId, scrollFont); // We need to use a TrueType monospace font to display debug texts properly. loadFont(monoFontId, "DejaVuLGCSansMono");
// Use our TrueType fonts as a fallback. if (!MyGUI::ResourceManager::getInstance().isExist(defaultFontId.mValue) && !Misc::StringUtils::ciEqual(defaultFont, "MysticCards")) loadFont(defaultFontId, "MysticCards");
if (!MyGUI::ResourceManager::getInstance().isExist(scrollFontId.mValue) && !Misc::StringUtils::ciEqual(scrollFont, "DemonicLetters")) loadFont(scrollFontId, "DemonicLetters"); }
void FontLoader::loadFont(FontId fontId, std::string_view fileName) { constexpr VFS::Path::NormalizedView fonts("fonts");
constexpr VFS::Path::ExtensionView fnt("fnt"); const VFS::Path::Normalized fntPath = VFS::Path::join(fonts, fileName, fnt);
if (const Files::IStreamPtr stream = mVFS->find(fntPath)) { loadBitmapFont(fontId, fntPath, *stream); return; }
constexpr VFS::Path::ExtensionView omwfont("omwfont"); const VFS::Path::Normalized omwfontPath = VFS::Path::join(fonts, fileName, omwfont);
if (const Files::IStreamPtr stream = mVFS->find(omwfontPath)) { loadTrueTypeFont(fontId, omwfontPath, *stream); return; }
Log(Debug::Error) << "Font '" << fileName << "' is not found."; }
void FontLoader::loadTrueTypeFont(FontId fontId, const VFS::Path::Normalized& path, std::istream& stream) { Log(Debug::Info) << "Loading TrueType font file " << path;
MyGUIPlatform::DataManager* const dataManager = dynamic_cast<MyGUIPlatform::DataManager*>(&MyGUIPlatform::DataManager::getInstance()); if (dataManager == nullptr) { Log(Debug::Error) << "Can not load TrueType font " << fontId.mValue << ": osgMyGUI::DataManager is not available."; return; }
// TODO: it may be worth to take in account resolution change, but it is not safe to replace used assets std::unique_ptr<MyGUI::IDataStream> layersStream(dataManager->getData("openmw_layers.xml")); MyGUI::IntSize bookSize = getBookSize(layersStream.get()); float bookScale = MyGUIPlatform::ScalingLayer::getScaleFactor(bookSize); const VFS::Path::Normalized oldDataPath(dataManager->getResourcePath());
struct SetOldResourcePath { VFS::Path::NormalizedView mOldDataPath;
void operator()(MyGUIPlatform::DataManager* dataManager) const { dataManager->setResourcePath(mOldDataPath); } };
std::unique_ptr<MyGUIPlatform::DataManager, SetOldResourcePath> dataManagerPtr( dataManager, SetOldResourcePath{ oldDataPath });
constexpr VFS::Path::NormalizedView fonts("fonts"); dataManager->setResourcePath(fonts);
std::unique_ptr<MyGUI::IDataStream> dataStream = std::make_unique<MyGUI::DataStream>(&stream);
MyGUI::xml::Document xmlDocument; xmlDocument.open(dataStream.get()); MyGUI::xml::ElementPtr root = xmlDocument.getRoot();
MyGUI::xml::ElementEnumerator resourceNode = root->getElementEnumerator(); bool valid = false; if (resourceNode.next("Resource")) { valid = resourceNode->findAttribute("type") == "ResourceTrueTypeFont"; }
if (valid == false) { Log(Debug::Error) << "Can not load TrueType font " << fontId.mValue << ": " << path << " is invalid."; return; }
int resolution = 70; MyGUI::xml::ElementPtr resolutionNode = getProperty(resourceNode.current(), "Resolution"); if (resolutionNode == nullptr) { resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); } else resolution = MyGUI::utility::parseInt(resolutionNode->findAttribute("value"));
resolutionNode->setAttribute("value", MyGUI::utility::toString(resolution * std::ceil(mScalingFactor)));
MyGUI::xml::ElementPtr sizeNode = resourceNode->createChild("Property"); sizeNode->addAttribute("key", "Size"); sizeNode->addAttribute("value", std::to_string(Settings::gui().mFontSize));
MyGUI::ResourceTrueTypeFont* font = static_cast<MyGUI::ResourceTrueTypeFont*>( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceTrueTypeFont")); font->deserialization(resourceNode.current(), MyGUI::Version(3, 2, 0)); font->setResourceName(fontId.mValue); MyGUI::ResourceManager::getInstance().addResource(font);
resolutionNode->setAttribute( "value", MyGUI::utility::toString(static_cast<int>(resolution * bookScale * mScalingFactor)));
MyGUI::ResourceTrueTypeFont* bookFont = static_cast<MyGUI::ResourceTrueTypeFont*>( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceTrueTypeFont")); bookFont->deserialization(resourceNode.current(), MyGUI::Version(3, 2, 0)); bookFont->setResourceName(std::format("Journalbook {}", fontId.mValue)); MyGUI::ResourceManager::getInstance().addResource(bookFont);
dataManagerPtr.reset();
if (resourceNode.next("Resource")) Log(Debug::Warning) << "Font file " << path << " contains multiple Resource entries, only first one will be used."; }
typedef struct { float x; float y; } Point;
typedef struct { float mUnknown; // appears unused, always 0 Point mTopLeft; Point mTopRight; Point mBottomLeft; Point mBottomRight; float mWidth; float mHeight; float mKerningLeft; float mKerningRight; float mAscent; } GlyphInfo;
void FontLoader::loadBitmapFont(FontId fontId, const VFS::Path::Normalized& path, std::istream& stream) { Log(Debug::Info) << "Loading bitmap font file " << path;
float fontSize; stream.read(reinterpret_cast<char*>(&fontSize), sizeof(fontSize)); if (!stream.good()) fail(stream, path, "File too small to be a valid font");
int one; stream.read(reinterpret_cast<char*>(&one), sizeof(one)); if (!stream.good()) fail(stream, path, "File too small to be a valid font");
if (one != 1) fail(stream, path, "Unexpected value");
stream.read(reinterpret_cast<char*>(&one), sizeof(one)); if (!stream.good()) fail(stream, path, "File too small to be a valid font");
if (one != 1) fail(stream, path, "Unexpected value");
char nameBuffer[284]; stream.read(nameBuffer, sizeof(nameBuffer)); if (!stream.good()) fail(stream, path, "File too small to be a valid font");
GlyphInfo data[256]; stream.read((char*)data, sizeof(data)); if (!stream.good()) fail(stream, path, "File too small to be a valid font");
// Create the font texture const std::string name(nameBuffer);
constexpr VFS::Path::NormalizedView fonts("fonts"); constexpr VFS::Path::ExtensionView tex("tex"); const VFS::Path::Normalized bitmapPath = VFS::Path::join(fonts, name, tex);
Files::IStreamPtr bitmapFile = mVFS->get(bitmapPath);
int width; bitmapFile->read(reinterpret_cast<char*>(&width), sizeof(int));
int height; bitmapFile->read(reinterpret_cast<char*>(&height), sizeof(int));
if (!bitmapFile->good()) fail(*bitmapFile, bitmapPath, "File too small to be a valid bitmap");
if (width <= 0 || height <= 0) fail(*bitmapFile, bitmapPath, "Width and height must be positive");
std::vector<char> textureData; textureData.resize(width * height * 4); bitmapFile->read(textureData.data(), width * height * 4); if (!bitmapFile->good()) Log(Debug::Warning) << "Font bitmap " << bitmapPath << " ended prematurely, using partial data (" << bitmapFile->gcount() << "/" << (width * height * 4) << " bytes)"; bitmapFile.reset();
if (mExportFonts) { osg::ref_ptr<osg::Image> image = new osg::Image; image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); assert(image->isDataContiguous()); memcpy(image->data(), textureData.data(), textureData.size()); // Convert to OpenGL origin for sensible output image->flipVertical();
Log(Debug::Info) << "Writing " << name + ".png"; osgDB::writeImageFile(*image, name + ".png"); }
MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().createTexture(bitmapPath); texture->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); unsigned char* texData = reinterpret_cast<unsigned char*>(texture->lock(MyGUI::TextureUsage::Write)); memcpy(texData, textureData.data(), textureData.size()); texture->unlock();
// We need to emulate loading from XML because the data members are private as of mygui 3.2.0 MyGUI::xml::Document xmlDocument; MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont");
root->addAttribute("name", fontId.mValue);
MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property"); defaultHeight->addAttribute("key", "DefaultHeight"); defaultHeight->addAttribute("value", fontSize); MyGUI::xml::ElementPtr source = root->createChild("Property"); source->addAttribute("key", "Source"); source->addAttribute("value", bitmapPath); MyGUI::xml::ElementPtr codes = root->createChild("Codes");
// Fall back from unavailable Win-1252 encoding symbols to similar characters available in CP-437 game fonts // Generally, that is, as this tries to follow Morrowind's interesting substitution logic
using FallbackIndex = unsigned int; using UnicodeIndex = unsigned int; std::multimap<FallbackIndex, UnicodeIndex> additional;
// Some symbols must not be shown altogether std::vector<UnicodeIndex> omitted;
// Ignored madness: caret ^ (available in the font!) uses percent sign % glyph // We do not differentiate between glyphs that are always considered missing and glyphs that are not replaced // because there's (probably?) no point
// € (Euro Sign, 0x80/U+20AC) is replaced with underscore // 0x81 (unused) is replaced with underscore additional.emplace(44, 0x201A); // ‚ (Single Low-9 Quotation Mark, 0x82) => , (comma) // ƒ (Latin Small Letter F with Hook, 0x83) is unavailable, not replaced additional.emplace(44, 0x201E); // „ (Double Low-9 Quotation Mark, 0x84) => , (comma) additional.emplace(46, 0x2026); // … (Horizontal Ellipsis, 0x85) => . (period) additional.emplace(43, 0x2020); // † (Dagger, 0x86) => + (plus sign) additional.emplace(216, 0x2021); // ‡ (Double Dagger, 0x87) => ╫ additional.emplace(94, 0x02C6); // ˆ (Modifier Letter Circumflex Accent, 0x88) => ^ (caret) additional.emplace(37, 0x2030); // ‰ (Per Mille Sign, 0x89) => % (percent sign) additional.emplace(83, 0x0160); // Š (Latin Capital Letter S with Caron, 0x8A) => S (latin capital S) additional.emplace(60, 0x2039); // ‹ (Single Left-Pointing Angle Quotation Mark, 0x8B) => < (less-than) additional.emplace(79, 0x0152); // Œ (Latin Capital Ligature Oe, 0x8C) => O (latin capital O) // 0x8D (unused) is replaced with underscore additional.emplace(90, 0x017D); // Ž (Latin Capital Letter Z with Caron, 0x8E) => Z (latin capital Z) // 0x8F (unused) is replaced with underscore // 0x90 (unused) is replaced with underscore additional.emplace(39, 0x2018); // ‘ (Left Single Quotation Mark, 0x91) => ' (apostrophe) (MW: backtick) additional.emplace(39, 0x2019); // ’ (Right Single Quotation Mark, 0x92) => ' (apostrophe) additional.emplace(34, 0x201C); // “ (Left Double Quotation Mark, 0x93) => " (double quote mark) additional.emplace(34, 0x201D); // ” (Right Double Quotation Mark, 0x94) => " (double quote mark) omitted.push_back(0x2022); // • (Bullet, 0x95) additional.emplace(45, 0x2013); // – (En Dash, 0x96) => - (hyphen) additional.emplace(45, 0x2014); // — (Em Dash, 0x97) => - (hyphen) additional.emplace(126, 0x02DC); // ˜ (Small Tilde, 0x98) => ~ (tilde) additional.emplace(84, 0x2122); // ™ (Trade Mark Sign, 0x99) => T (latin capital T) additional.emplace(115, 0x0161); // š (Latin Small Letter S with Caron, 0x9A) => s (latin small S) additional.emplace(62, 0x203A); // › (Single Right-Pointing Angle Quotation Mark, 0x9B) => > (greater-than) additional.emplace(111, 0x0153); // œ (Latin Small Ligature Oe, 0x9C) => o (latin small O) // 0x9D (unused) is replaced with underscore additional.emplace(122, 0x017E); // ž (Latin Small Letter Z with Caron, 0x9E) => z (latin small Z) additional.emplace(89, 0x0178); // Ÿ (Latin Capital Letter Y with Diaeresis, 0x9F) => Y (latin capital Y) // Nbsp (No-Break Space, 0xA0) is unavailable, not replaced // ¡ (Inverted Exclamation Mark, 0xA1) is unavailable, not replaced // ¢ (Cent Sign, 0xA2) is unavailable, not replaced // £ (Pound Sign, 0xA3) is available but its glyph looks like œ (small oe ligature) omitted.push_back(0x00A4); // ¤ (Currency Sign) // ¥ (Yen Sign, 0xA5) is unavailable, not replaced additional.emplace(221, 0x00A6); // ¦ (Broken Bar, 0xA6) => ▌ omitted.push_back(0x00A7); // § (Section Sign) additional.emplace(34, 0x00A8); // ¨ (Diaeresis) => " (double quote mark) additional.emplace(99, 0x00A9); // © (Copyright Sign) => c (latin small C) // ª (Feminine Ordinal Indicator, 0xAA) is unavailable, not replaced // « (Left-Pointing Double Angle Quotation Mark, 0xAB) is unavailable, not replaced // ¬ (Not Sign, 0xAC) is unavailable, not replaced additional.emplace(45, 0x00AD); // Soft Hyphen => - (hyphen) additional.emplace(114, 0x00AE); // ® (Registered Sign) => r (latin small R) additional.emplace(95, 0x00AF); // ¯ (Macron) => _ (underscore) // ° (Degree Sign, 0xB0) is unavailable, not replaced // ± (Plus-Minus Sign, 0xB1) is unavailable, not replaced // ² (Superscript Two, 0xB2) is unavailable, not replaced additional.emplace(51, 0x00B3); // ³ (Superscript Three) => 3 (three digit) additional.emplace(39, 0x00B4); // ´ (Acute Accent) => ' (apostrophe) // µ (Micro Sign, 0xB5) is unavailable, not replaced omitted.push_back(0x00B6); // ¶ (Pilcrow Sign) // · (Middle Dot, 0xB7) is unavailable, not replaced additional.emplace(44, 0x00B8); // ¸ (Cedilla) => , (comma) additional.emplace(49, 0x00B9); // ¹ (Superscript One) => 1 (one digit) // º (Masculine Ordinal Indicator, 0xBA) is unavailable, not replaced // » (Right-Pointing Double Angle Quotation Mark, 0xBB) is unavailable, not replaced // ¼ (Vulgar Fraction One Quarter, 0xBC) is unavailable, not replaced // ½ (Vulgar Fraction One Half, 0xBD) is unavailable, not replaced // ¾ (Vulgar Fraction Three Quarters, 0xBE) is replaced with underscore // ¿ (Inverted Question Mark, 0xBF) is unavailable, not replaced additional.emplace(65, 0x00C0); // À (Latin Capital Letter A with Grave) => A (latin capital A) additional.emplace(65, 0x00C1); // Á (Latin Capital Letter A with Acute) => A (latin capital A) additional.emplace(65, 0x00C2); //  (Latin Capital Letter A with Circumflex) => A (latin capital A) additional.emplace(65, 0x00C3); // à (Latin Capital Letter A with Tilde) => A (latin capital A) // Ä (Latin Capital Letter A with Diaeresis, 0xC4) is available // Å (Latin Capital Letter A with Ring Above, 0xC5) is available // Æ (Latin Capital Letter Ae, 0xC6) is unavailable, not replaced // Ç (Latin Capital Letter C with Cedilla, 0xC7) is available additional.emplace(69, 0x00C8); // È (Latin Capital Letter E with Grave) => E (latin capital E) // É (Latin Capital Letter E with Acute, 0xC9) is available additional.emplace(69, 0x00CA); // Ê (Latin Capital Letter E with Circumflex) => E (latin capital E) additional.emplace(69, 0x00CB); // Ë (Latin Capital Letter E with Diaeresis) => E (latin capital E) additional.emplace(73, 0x00CC); // Ì (Latin Capital Letter I with Grave) => I (latin capital I) additional.emplace(73, 0x00CD); // Í (Latin Capital Letter I with Acute) => I (latin capital I) additional.emplace(73, 0x00CE); // Î (Latin Capital Letter I with Circumflex) => I (latin capital I) additional.emplace(73, 0x00CF); // Ï (Latin Capital Letter I with Diaeresis) => I (latin capital I) additional.emplace(68, 0x00D0); // Ð (Latin Capital Letter Eth) => D (latin capital D) // Ñ (Latin Capital Letter N with Tilde, 0xD1) is unavailable, not replaced additional.emplace(79, 0x00D2); // Ò (Latin Capital Letter O with Grave) => O (latin capital O) additional.emplace(79, 0x00D3); // Ó (Latin Capital Letter O with Acute) => O (latin capital O) additional.emplace(79, 0x00D4); // Ô (Latin Capital Letter O with Circumflex) => O (latin capital O) additional.emplace(79, 0x00D5); // Õ (Latin Capital Letter O with Tilde) => O (latin capital O) // Ö (Latin Capital Letter O with Diaeresis, 0xD6) is available additional.emplace(120, 0x00D7); // × (Multiplication Sign) => x (latin small X) additional.emplace(79, 0x00D8); // Ø (Latin Capital Letter O with Stroke) => O (latin capital O) additional.emplace(85, 0x00D9); // Ù (Latin Capital Letter U with Grave) => U (latin capital U) additional.emplace(85, 0x00DA); // Ú (Latin Capital Letter U with Acute) => U (latin capital U) additional.emplace(85, 0x00DB); // Û (Latin Capital Letter U with Circumflex) => U (latin capital U) // Ü (Latin Capital Letter U with Diaeresis, 0xDC) is available additional.emplace(89, 0x00DD); // Ý (Latin Capital Letter Y with Acute) => Y (latin capital Y) // 0xDE to 0xFF are generally not replaced with certain exceptions additional.emplace(97, 0x00E3); // ã (Latin Small Letter A with Tilde) => a (latin small A) additional.emplace(100, 0x00F0); // ð (Latin Small Letter Eth) => d (latin small D) additional.emplace(111, 0x00F5); // õ (Latin Small Letter O with Tilde) => o (latin small O) additional.emplace(111, 0x00F8); // ø (Latin Small Letter O with Stroke) => o (latin small O) additional.emplace(121, 0x00FD); // ý (Latin Small Letter Y with Acute) => y (latin small Y)
// Russian Morrowind which uses Win-1251 encoding only does equivalent (often garbage) Win-1252 replacements // However, we'll provide custom replacements for Cyrillic io letters // These letters aren't a part of any content but they are printable additional.emplace(69, 0x0401); // Ё (Cyrillic Capital Letter Io) => E (latin capital E) additional.emplace(137, 0x0451); // ё (Cyrillic Small Letter Io) => ë (latin small E-diaeresis)
// ASCII vertical bar, use this as text input cursor additional.emplace(124, MyGUI::FontCodeType::Cursor);
// Underscore, use for NotDefined marker (used for glyphs not existing in the font) additional.emplace(95, MyGUI::FontCodeType::NotDefined);
for (unsigned i = 0; i < 256; i++) { float x1 = data[i].mTopLeft.x * width; float y1 = data[i].mTopLeft.y * height; float w = data[i].mTopRight.x * width - x1; float h = data[i].mBottomLeft.y * height - y1;
ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = getUnicode(static_cast<unsigned char>(i), encoder, mEncoding); const std::string coord = MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h); float advance = data[i].mWidth + data[i].mKerningRight; // Yes MyGUI, we really do want an advance of 0 sometimes, thank you. if (advance == 0.f && data[i].mWidth != 0.f) advance = std::numeric_limits<float>::min(); const std::string bearing = MyGUI::utility::toString(data[i].mKerningLeft) + ' ' + MyGUI::utility::toString((fontSize - data[i].mAscent)); const MyGUI::IntSize size(static_cast<int>(data[i].mWidth), static_cast<int>(data[i].mHeight));
MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); code->addAttribute("coord", coord); code->addAttribute("advance", advance); code->addAttribute("bearing", bearing); code->addAttribute("size", size);
for (auto [it, end] = additional.equal_range(i); it != end; ++it) { code = codes->createChild("Code"); code->addAttribute("index", it->second); code->addAttribute("coord", coord); code->addAttribute("advance", advance); code->addAttribute("bearing", bearing); code->addAttribute("size", size); } }
// These are required as well, but the fonts don't provide them omitted.push_back(MyGUI::FontCodeType::Selected); omitted.push_back(MyGUI::FontCodeType::SelectedBack); for (const UnicodeIndex index : omitted) { MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", index); code->addAttribute("coord", "0 0 0 0"); code->addAttribute("advance", "0"); code->addAttribute("bearing", "0 0"); code->addAttribute("size", "0 0"); }
if (mExportFonts) { Log(Debug::Info) << "Writing " << name + ".xml"; xmlDocument.createDeclaration(); xmlDocument.save(name + ".xml"); }
// Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); font->deserialization(root, MyGUI::Version(3, 2, 0));
MyGUI::ResourceManualFont* bookFont = static_cast<MyGUI::ResourceManualFont*>( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); bookFont->deserialization(root, MyGUI::Version(3, 2, 0)); bookFont->setResourceName(std::format("Journalbook {}", fontId.mValue));
MyGUI::ResourceManager::getInstance().addResource(font); MyGUI::ResourceManager::getInstance().addResource(bookFont); }
void FontLoader::overrideLineHeight(MyGUI::xml::ElementPtr node, std::string_view file, MyGUI::Version version) { // We should adjust line height for MyGUI widgets depending on font size MyGUI::xml::ElementEnumerator resourceNode = node->getElementEnumerator(); while (resourceNode.next("Resource")) { auto type = resourceNode->findAttribute("type");
if (Misc::StringUtils::ciEqual(type, "ResourceLayout")) { MyGUI::xml::ElementEnumerator resourceRootNode = resourceNode->getElementEnumerator(); while (resourceRootNode.next("Widget")) { if (resourceRootNode->findAttribute("name") != "Root") continue;
MyGUI::xml::ElementPtr heightNode = resourceRootNode->createChild("UserString"); heightNode->addAttribute("key", "HeightLine"); heightNode->addAttribute("value", std::to_string(Settings::gui().mFontSize + 2));
break; } } }
MyGUI::ResourceManager::getInstance().loadFromXmlNode(node, file, version); }
std::string_view FontLoader::getFontForFace(std::string_view face) { if (Misc::StringUtils::ciEqual(face, "daedric")) return "ScrollFont";
return "DefaultFont"; }}