Convert text drawn on the map to ISO8859-2 (which is what libgd uses)

Using non-ISO8859-2 encoded text (e.g. utf-8), or anything else,
may result in garbage characters.
master
Rogier 2016-01-06 12:35:13 +01:00
parent e47e3bcd3a
commit 570ed84fd2
12 changed files with 259 additions and 8 deletions

View File

@ -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)

View File

@ -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

37
CharEncodingConverter.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <iostream>
#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
}

42
CharEncodingConverter.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef _CHARENCODINGCONVERTER_H_INCLUDED_
#define _CHARENCODINGCONVERTER_H_INCLUDED_
#include <string>
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_

View File

@ -0,0 +1,97 @@
#include "CharEncodingConverterIConv.h"
#include <cstring>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <langinfo.h>
#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<char *>(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;
}

View File

@ -0,0 +1,21 @@
#ifndef _CHARENCODINGCONVERTERICONV_H_INCLUDED_
#define _CHARENCODINGCONVERTERICONV_H_INCLUDED_
#include <iconv.h>
#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_

View File

@ -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<unsigned char *>(const_cast<char *>(player->name.c_str())), color);
gdImageString(m_image, gdFontGetMediumBold(), imageX + 2, imageY + 2, reinterpret_cast<unsigned char *>(const_cast<char *>(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<unsigned char *>(const_cast<char *>(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<unsigned char *>(const_cast<char *>(displayText.c_str())), o->color.to_libgd());
}
break;
default:
#ifdef DEBUG

View File

@ -25,6 +25,7 @@
#include <string>
#include <iostream>
#include <sstream>
#include "CharEncodingConverter.h"
#include "types.h"
#include "PixelAttributes.h"
#include "BlockPos.h"
@ -317,6 +318,8 @@ private:
uint16_t m_readedPixels[16];
std::set<std::string> m_unknownNodes;
std::vector<DrawObject> m_drawObjects;
CharEncodingConverter *m_gdStringConv;
}; /* ----- end of class TileGenerator ----- */
#endif /* end of include guard: TILEGENERATOR_H_JJNUCARH */

View File

@ -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@"

View File

@ -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.

View File

@ -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

View File

@ -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);