diff --git a/CMakeLists.txt b/CMakeLists.txt index 35ad76b..4d2df58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(minetestmapper CXX) +project(minetestmapper CXX C) cmake_minimum_required(VERSION 2.6) cmake_policy(SET CMP0003 NEW) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -139,6 +139,21 @@ if(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR) message(SEND_ERROR "zlib not found!") endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR) +# Find iconv +OPTION(ENABLE_ICONV "Enable character encoding conversion of text written on the map" True) +if(ENABLE_ICONV) + find_package(Iconv) + if(ICONV_FOUND) + set(USE_ICONV 1) + set(MAPPER_SRCS_ICONV CharEncodingConverterIConv.cpp) + message (STATUS "iconv libraries: ${ICONV_LIBRARIES}") + else(ICONV_FOUND) + message (SEND_ERROR "iconv libraries not found") + endif(ICONV_FOUND) +else(ENABLE_ICONV) + set(USE_ICONV 0) +endif(ENABLE_ICONV) + find_package(PkgConfig) include(FindPackageHandleStandardArgs) @@ -245,6 +260,7 @@ include_directories( "${CMAKE_CURRENT_BINARY_DIR}" ${LIBGD_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} + ${ICONV_INCLUDE_DIR} ) set(mapper_SRCS @@ -256,12 +272,15 @@ set(mapper_SRCS Settings.cpp BlockPos.cpp mapper.cpp + CharEncodingConverter.cpp + ${MAPPER_SRCS_ICONV} ) set(LINK_LIBRARIES minetestmapper ${LIBGD_LIBRARY} ${ZLIB_LIBRARY} + ${ICONV_LIBRARIES} ) if(USE_SQLITE3) diff --git a/Changelog b/Changelog index 35596d8..5a5fc73 100644 --- a/Changelog +++ b/Changelog @@ -17,9 +17,14 @@ - Command-line options are now all case-agnostic wrt their parameters. - Allow nodes to be defined as air-type or ignore-type in colors.txt - Added an option to draw 'ignore'-nodes. + - Text drawn on the map is now converted to the ISO8859-2 encoding. Due + to limited font support in the drawing library, characters not + in the ISO8859-2 set are not supported. + Minetestmapper will attempt to render unsupported characters + sensibly if it can. Bugfixes: - Fixed possible compilation failure caused by stdint.h - - Fixed compilation failure when some database libraries are not installed + - Fixed compilation failure when some database libraries are not installed. Even when support for some of the databases was not enabled, the libraries for those databases still needed to be installed on the system for compilation to succeed. Now the libraries no diff --git a/CharEncodingConverter.cpp b/CharEncodingConverter.cpp new file mode 100644 index 0000000..c03ec03 --- /dev/null +++ b/CharEncodingConverter.cpp @@ -0,0 +1,37 @@ + +#include +#include "cmake_config.h" + +#include "CharEncodingConverter.h" +#ifdef _WIN32 +#warning No standard charset converter defined for WIN32 - disabling conversion +#else +#include "CharEncodingConverterIConv.h" +#endif + +CharEncodingConverter *CharEncodingConverter::createStandardConverter(const std::string to, const std::string from) +{ +#ifdef _WIN32 + return new CharEncodingConverterDummy(to, from); +#else + #if USE_ICONV + return new CharEncodingConverterIConv(to, from); + #else + return new CharEncodingConverterDummy(to, from); + #endif +#endif +} + +std::string CharEncodingConverter::getCurrentCharEncoding(void) +{ +#ifdef _WIN32 + return CharEncodingConverterDummy::getCurrentCharEncoding(); +#else + #if USE_ICONV + return CharEncodingConverterIConv::getCurrentCharEncoding(); + #else + return CharEncodingConverterDummy::getCurrentCharEncoding(); + #endif +#endif +} + diff --git a/CharEncodingConverter.h b/CharEncodingConverter.h new file mode 100644 index 0000000..658cad5 --- /dev/null +++ b/CharEncodingConverter.h @@ -0,0 +1,42 @@ + +#ifndef _CHARENCODINGCONVERTER_H_INCLUDED_ +#define _CHARENCODINGCONVERTER_H_INCLUDED_ + +#include + +class CharEncodingConverter +{ +public: + CharEncodingConverter(const std::string to, const std::string from) + : m_toFormat(to), m_fromFormat(from) { + if (m_toFormat == "") m_toFormat = getCurrentCharEncoding(); + if (m_fromFormat == "") m_fromFormat = getCurrentCharEncoding(); + } + virtual ~CharEncodingConverter(void) {} + + // Create a converter with an unspecified but suitable backend. + // (for usage convenience) + static CharEncodingConverter *createStandardConverter(const std::string to, const std::string from = ""); + static std::string getCurrentCharEncoding(void); + + virtual std::string convert(const std::string &src) = 0; + virtual std::string fromFormat(void) { return m_fromFormat; } + virtual std::string toFormat(void) { return m_toFormat; } +protected: + std::string m_toFormat; + std::string m_fromFormat; +}; + +class CharEncodingConverterDummy : public CharEncodingConverter +{ +public: + CharEncodingConverterDummy(const std::string to, const std::string from = "") + : CharEncodingConverter(to, from) {} + virtual ~CharEncodingConverterDummy(void) {} + + static std::string getCurrentCharEncoding(void) { return "UTF-8"; } + std::string convert(const std::string &src) override { return src; } +}; + +#endif // _CHARENCODINGCONVERTER_H_INCLUDED_ + diff --git a/CharEncodingConverterIConv.cpp b/CharEncodingConverterIConv.cpp new file mode 100644 index 0000000..eb5269b --- /dev/null +++ b/CharEncodingConverterIConv.cpp @@ -0,0 +1,97 @@ + +#include "CharEncodingConverterIConv.h" + +#include +#include +#include +#include +#include + +#define ICONV_BUFSIZE 16 + +std::string CharEncodingConverterIConv::getCurrentCharEncoding(void) +{ + setlocale(LC_CTYPE, ""); + char *enc = nl_langinfo(CODESET); + std::string encoding; + if (*enc) { + if (std::string(enc) == "ANSI_X3.4-1968") + encoding = "US-ASCII"; + else + encoding = enc; + } + else { + std::cerr << "WARNING: could not determine current character encoding. Assuming UTF-8" << std::endl; + encoding = "UTF-8"; + } + // Reset locale to 'C'. + // Advantage: unknown characters are converted to '?' instead of causing a failure... + // Disadvantage: transliteration support may be more limited. + // Alternative: modify conversion to replace unknown chars with '?' manually. + setlocale(LC_CTYPE, "C"); + return encoding; +} + + +CharEncodingConverterIConv::CharEncodingConverterIConv(std::string to, std::string from) + : CharEncodingConverter(to, from) +{ + to = m_toFormat + "//TRANSLIT"; + from = m_fromFormat; + m_iconv = iconv_open(to.c_str(), from.c_str()); + if (m_iconv == (iconv_t) -1) { + int rno = errno; + std::string msg = std::string("Error initializing iconv charset converter (") + + (from=="" ? std::string("(default)") : from) + " --> " + + (to=="" ? std::string("(default)") : to) + "): " + strerror(rno); + throw std::runtime_error(msg); + } +} + +CharEncodingConverterIConv::~CharEncodingConverterIConv(void) +{ + if (m_iconv != (iconv_t) -1) + iconv_close(m_iconv); +} + +std::string CharEncodingConverterIConv::convert(const std::string &src) +{ + std::string dst; + + char toBuffer[ICONV_BUFSIZE + 1]; + char *fromBufP, *toBufP; + size_t fromBufLen, toBufLen; + + fromBufLen = src.length(); + // Assume that iconv() does not write to the source array... + fromBufP = const_cast(src.c_str()); + toBufLen = ICONV_BUFSIZE; + toBufP = toBuffer; + + size_t rv; + do { + rv = iconv(m_iconv, &fromBufP, &fromBufLen, &toBufP, &toBufLen); + if (rv == (size_t) -1) { + int rno = errno; + if (rno != E2BIG) { + std::ostringstream oss; + oss << "Failure converting character from " + << fromFormat() << " to " << toFormat() + << " (text: '[" << std::string(src.c_str(), 0, fromBufP - src.c_str()) << "]" << std::string(fromBufP) << "'): " + << strerror(rno); + // Note: strerror() can be misleading, e.g. complaining about invalid input + // when really the character cannot be represented in the output... + // (but //TRANSLIT avoids (most of?) those kinds of errors...) + throw std::runtime_error(oss.str()); + } + } + dst += std::string(toBuffer, ICONV_BUFSIZE - toBufLen); + toBufLen = ICONV_BUFSIZE; + toBufP = toBuffer; + } while (rv == (size_t) -1); + + iconv(m_iconv, NULL, NULL, NULL, NULL); + + return dst; +} + diff --git a/CharEncodingConverterIConv.h b/CharEncodingConverterIConv.h new file mode 100644 index 0000000..40edb48 --- /dev/null +++ b/CharEncodingConverterIConv.h @@ -0,0 +1,21 @@ + +#ifndef _CHARENCODINGCONVERTERICONV_H_INCLUDED_ +#define _CHARENCODINGCONVERTERICONV_H_INCLUDED_ + +#include +#include "CharEncodingConverter.h" + +class CharEncodingConverterIConv : public CharEncodingConverter +{ +public: + CharEncodingConverterIConv(std::string to, std::string from = ""); + virtual ~CharEncodingConverterIConv(void); + + static std::string getCurrentCharEncoding(void); + std::string convert(const std::string &src) override; +private: + iconv_t m_iconv; +}; + +#endif // _CHARENCODINGCONVERTERICONV_H_INCLUDED_ + diff --git a/TileGenerator.cpp b/TileGenerator.cpp index edf6713..db15b1c 100644 --- a/TileGenerator.cpp +++ b/TileGenerator.cpp @@ -186,8 +186,13 @@ TileGenerator::TileGenerator(): m_tileMapXOffset(0), m_tileMapYOffset(0), m_surfaceHeight(INT_MIN), - m_surfaceDepth(INT_MAX) + m_surfaceDepth(INT_MAX), + m_gdStringConv(NULL) { + // Libgd requires ISO8859-2 :-( + // Internally, we use UTF-8. Assume minetest does the same... (if not, it's probably broken) + m_gdStringConv = CharEncodingConverter::createStandardConverter("ISO8859-2", "UTF-8"); + memset(&m_databaseFormatFound, 0, sizeof(m_databaseFormatFound)); // Load default grey colors. m_heightMapColors.push_back(HeightMapColor(INT_MIN, Color(0,0,0), -129, Color(0,0,0))); @@ -2239,9 +2244,10 @@ void TileGenerator::renderPlayers(const std::string &inputPath) for (PlayerAttributes::Players::iterator player = players.begin(); player != players.end(); ++player) { int imageX = worldX2ImageX(player->x / 10); int imageY = worldZ2ImageY(player->z / 10); + std::string displayName = m_gdStringConv->convert(player->name); gdImageArc(m_image, imageX, imageY, 5, 5, 0, 360, color); - gdImageString(m_image, gdFontGetMediumBold(), imageX + 2, imageY + 2, reinterpret_cast(const_cast(player->name.c_str())), color); + gdImageString(m_image, gdFontGetMediumBold(), imageX + 2, imageY + 2, reinterpret_cast(const_cast(displayName.c_str())), color); } } @@ -2376,8 +2382,11 @@ void TileGenerator::renderDrawObjects(void) case DrawObject::Rectangle: gdImageRectangle(m_image, o->corner1.x(), o->corner1.y(), o->corner2.x(), o->corner2.y(), o->color.to_libgd()); break; - case DrawObject::Text: - gdImageString(m_image, gdFontGetMediumBold(), o->center.x(), o->center.y(), reinterpret_cast(const_cast(o->text.c_str())), o->color.to_libgd()); + case DrawObject::Text: { + std::string displayText = m_gdStringConv->convert(o->text.c_str()); + gdImageString(m_image, gdFontGetMediumBold(), o->center.x(), o->center.y(), + reinterpret_cast(const_cast(displayText.c_str())), o->color.to_libgd()); + } break; default: #ifdef DEBUG diff --git a/TileGenerator.h b/TileGenerator.h index cdae7c9..c00c1bb 100644 --- a/TileGenerator.h +++ b/TileGenerator.h @@ -25,6 +25,7 @@ #include #include #include +#include "CharEncodingConverter.h" #include "types.h" #include "PixelAttributes.h" #include "BlockPos.h" @@ -317,6 +318,8 @@ private: uint16_t m_readedPixels[16]; std::set m_unknownNodes; std::vector m_drawObjects; + + CharEncodingConverter *m_gdStringConv; }; /* ----- end of class TileGenerator ----- */ #endif /* end of include guard: TILEGENERATOR_H_JJNUCARH */ diff --git a/cmake_config.h.in b/cmake_config.h.in index b63efe0..f8f64c9 100644 --- a/cmake_config.h.in +++ b/cmake_config.h.in @@ -8,6 +8,8 @@ #define USE_LEVELDB @USE_LEVELDB@ #define USE_REDIS @USE_REDIS@ +#define USE_ICONV @USE_ICONV@ + #define VERSION_MAJOR "@VERSION_MAJOR@" #define VERSION_MINOR "@VERSION_MINOR@" diff --git a/doc/features.rst b/doc/features.rst index 8675f04..9438f82 100644 --- a/doc/features.rst +++ b/doc/features.rst @@ -110,3 +110,7 @@ Known Problems ``#4x91a1`` on another system) The cause of this difference has not been determined yet. + +* The drawing library supports ISO8859-2 fonts only. Characters in text and + player names that cannot be converted to the ISO8859-2 character set will + not be rendered correctly. diff --git a/doc/manual.rst b/doc/manual.rst index 4cab756..ff2f7c3 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -654,6 +654,11 @@ Detailed Description of Options '``$``', etc. On unix-like systems, use single quotes to avoid interpretation of most characters (except for ``'`` itself). + Due to a limitation of the drawing library, currently only text that + can be represented in (i.e. converted to) the ISO8859-2 character set is + supported. Text that uses non-compatible characters will not be rendered + correctly. + Note that the combination of geometry, color and text should be a single argument. This means that they must be enclosed in quotes together on the command-line, else they will be misinterpreted as three @@ -801,6 +806,9 @@ Detailed Description of Options The color can be set with `--origincolor`_. + Just like for text drawn with `--draw[map]text`_, characters that cannot be converted + to the ISO8859-2 character set will not be rendered correctly. + An image with a few players: .. image:: images/players.png diff --git a/mapper.cpp b/mapper.cpp index 19abc75..0bade1b 100644 --- a/mapper.cpp +++ b/mapper.cpp @@ -758,6 +758,8 @@ int main(int argc, char *argv[]) string heightMapNodesFile; bool foundGeometrySpec = false; bool setFixedOrShrinkGeometry = false; + CharEncodingConverter *charConvUTF8; + charConvUTF8 = CharEncodingConverter::createStandardConverter("UTF-8"); TileGenerator generator; try { @@ -1408,14 +1410,16 @@ int main(int argc, char *argv[]) if (object == 't') { iss >> std::ws; - std::getline(iss, drawObject.text); - if (drawObject.text.empty() || iss.fail()) { + std::string localizedText; + std::getline(iss, localizedText); + if (localizedText.empty() || iss.fail()) { std::cerr << "Invalid or missing text for " << long_options[option_index].name << " '" << optarg << "'" << std::endl; usage(); exit(1); } + drawObject.text = charConvUTF8->convert(localizedText); } generator.drawObject(drawObject);