Book formatting: Handle line endings as per vanilla, fix tall images causing infinite loop, cleanup

deque
MiroslavR 10 years ago
parent 84d27d55e2
commit c362ec0f95

@ -2,6 +2,7 @@
#include <components/interpreter/defines.hpp> #include <components/interpreter/defines.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp>
#include "../mwscript/interpretercontext.hpp" #include "../mwscript/interpretercontext.hpp"
@ -14,39 +15,167 @@
#include <MyGUI_EditText.h> #include <MyGUI_EditText.h>
namespace
{
Ogre::UTFString::unicode_char unicodeCharFromChar(char ch)
{
std::string s;
s += ch;
Ogre::UTFString string(s);
return string.getChar(0);
}
}
namespace MWGui namespace MWGui
{ {
namespace Formatting namespace Formatting
{ {
Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, std::string utf8Text, const int pageWidth, const int pageHeight) /* BookTextParser */
BookTextParser::BookTextParser(const std::string & text)
: mIndex(-1), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true)
{ {
using Ogre::UTFString;
MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor
utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext); mText = Interpreter::fixDefinesBook(mText, interpreterContext);
boost::algorithm::replace_all(mText, "\r", "");
registerTag("br", Event_BrTag);
registerTag("p", Event_PTag);
registerTag("img", Event_ImgTag);
registerTag("div", Event_DivTag);
registerTag("font", Event_FontTag);
}
void BookTextParser::registerTag(const std::string & tag, BookTextParser::Events type)
{
mTagTypes[tag] = type;
}
std::string BookTextParser::getReadyText()
{
return mReadyText;
}
BookTextParser::Events BookTextParser::next()
{
while (1)
{
++mIndex;
if (mIndex >= mText.size())
return Event_EOF;
char ch = mText[mIndex];
if (ch == '<')
{
const size_t tagStart = mIndex + 1;
const size_t tagEnd = mText.find('>', tagStart);
if (tagEnd == std::string::npos)
throw std::runtime_error("BookTextParser Error: Tag is not terminated");
parseTag(mText.substr(tagStart, tagEnd - tagStart));
mIndex = tagEnd;
if (mTagTypes.find(mTag) != mTagTypes.end())
{
Events type = mTagTypes.at(mTag);
if (type == Event_BrTag || type == Event_PTag)
{
if (!mIgnoreNewlineTags)
{
if (type == Event_BrTag)
mBuffer.push_back('\n');
else
{
mBuffer.append("\n\n");
}
}
mIgnoreLineEndings = true;
}
else
flushBuffer();
if (type == Event_ImgTag)
{
mIgnoreLineEndings = false;
mIgnoreNewlineTags = false;
}
return type;
}
}
else
{
if (!mIgnoreLineEndings || ch != '\n')
{
mBuffer.push_back(ch);
mIgnoreLineEndings = false;
mIgnoreNewlineTags = false;
}
}
if (mIndex == mText.size() - 1)
{
flushBuffer();
return Event_LastText;
}
}
}
void BookTextParser::flushBuffer()
{
mReadyText = mBuffer;
mBuffer.clear();
}
const BookTextParser::Attributes & BookTextParser::getAttributes() const
{
return mAttributes;
}
void BookTextParser::parseTag(std::string tag)
{
size_t tagNameEndPos = tag.find(' ');
mTag = tag.substr(0, tagNameEndPos);
Misc::StringUtils::toLower(mTag);
mAttributes.clear();
if (tagNameEndPos == std::string::npos)
return;
tag.erase(0, tagNameEndPos+1);
while (!tag.empty())
{
int sepPos = tag.find('=');
if (sepPos == std::string::npos)
return;
boost::algorithm::replace_all(utf8Text, "\n", ""); std::string key = tag.substr(0, sepPos);
boost::algorithm::replace_all(utf8Text, "\r", ""); Misc::StringUtils::toLower(key);
boost::algorithm::replace_all(utf8Text, "<BR>", "\n"); tag.erase(0, sepPos+1);
boost::algorithm::replace_all(utf8Text, "<P>", "\n\n");
UTFString text(utf8Text); std::string value;
UTFString plainText;
const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<'); if (tag.empty())
const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\n'); return;
if (tag[0] == '"')
{
size_t quoteEndPos = tag.find('"', 1);
if (quoteEndPos == std::string::npos)
throw std::runtime_error("BookTextParser Error: Missing end quote in tag");
value = tag.substr(1, quoteEndPos-1);
tag.erase(0, quoteEndPos+2);
}
else
{
size_t valEndPos = tag.find(' ');
if (valEndPos == std::string::npos)
{
value = tag;
tag.erase();
}
else
{
value = tag.substr(0, valEndPos);
tag.erase(0, valEndPos+1);
}
}
mAttributes[key] = value;
}
}
/* BookFormatter */
Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight)
{
Paginator pag(pageWidth, pageHeight); Paginator pag(pageWidth, pageHeight);
while (parent->getChildCount()) while (parent->getChildCount())
@ -57,55 +186,49 @@ namespace MWGui
MyGUI::Widget * paper = parent->createWidget<MyGUI::Widget>("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); MyGUI::Widget * paper = parent->createWidget<MyGUI::Widget>("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top);
paper->setNeedMouseFocus(false); paper->setNeedMouseFocus(false);
bool ignoreNewlines = true; BookTextParser parser(markup);
for (size_t index = 0; index < text.size(); ++index) BookTextParser::Events event;
while ((event = parser.next()) != BookTextParser::Event_EOF)
{ {
const UTFString::unicode_char ch = text.getChar(index); if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag)
if (!plainText.empty() && (ch == LEFT_ANGLE || index == text.size() - 1)) continue;
std::string plainText = parser.getReadyText();
if (!plainText.empty())
{ {
// if there's a newline at the end of the box caption, remove it // if there's a newline at the end of the box caption, remove it
if (plainText[plainText.size()-1] == NEWLINE) if (plainText[plainText.size()-1] == '\n')
plainText.erase(plainText.end()-1); plainText.erase(plainText.end()-1);
TextElement elem(paper, pag, mTextStyle, plainText.asUTF8()); TextElement elem(paper, pag, mTextStyle, plainText);
elem.paginate(); elem.paginate();
plainText.clear();
} }
if (ch == LEFT_ANGLE) switch (event)
{ {
const size_t tagStart = index + 1; case BookTextParser::Event_ImgTag:
const size_t tagEnd = text.find('>', tagStart);
if (tagEnd == UTFString::npos)
throw std::runtime_error("BookTextParser Error: Tag is not terminated");
const std::string tag = text.substr(tagStart, tagEnd - tagStart).asUTF8();
if (boost::algorithm::starts_with(tag, "IMG"))
{ {
ImageElement elem(paper, pag, mTextStyle, tag); const BookTextParser::Attributes & attr = parser.getAttributes();
elem.paginate();
ignoreNewlines = false; if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end())
} continue;
else if (boost::algorithm::starts_with(tag, "FONT"))
{
parseFont(tag);
}
else if (boost::algorithm::starts_with(tag, "DIV"))
{
parseDiv(tag);
}
index = tagEnd; std::string src = attr.at("src");
} int width = boost::lexical_cast<int>(attr.at("width"));
else int height = boost::lexical_cast<int>(attr.at("height"));
{
if (!ignoreNewlines || ch != NEWLINE) ImageElement elem(paper, pag, src, width, height);
{ elem.paginate();
plainText.push_back(ch); break;
ignoreNewlines = false;
} }
case BookTextParser::Event_FontTag:
handleFont(parser.getAttributes());
break;
case BookTextParser::Event_DivTag:
handleDiv(parser.getAttributes());
break;
default:
break;
} }
} }
@ -117,33 +240,31 @@ namespace MWGui
return pag.getPages(); return pag.getPages();
} }
Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, std::string utf8Text) Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup)
{ {
return markupToWidget(parent, utf8Text, parent->getWidth(), parent->getHeight()); return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight());
} }
void BookFormatter::parseDiv(std::string tag) void BookFormatter::handleDiv(const BookTextParser::Attributes & attr)
{ {
if (tag.find("ALIGN=") == std::string::npos) if (attr.find("align") == attr.end())
return; return;
int align_start = tag.find("ALIGN=")+7; std::string align = attr.at("align");
std::string align = tag.substr(align_start, tag.find('"', align_start)-align_start);
if (align == "CENTER") if (Misc::StringUtils::ciEqual(align, "center"))
mTextStyle.mTextAlign = MyGUI::Align::HCenter; mTextStyle.mTextAlign = MyGUI::Align::HCenter;
else if (align == "LEFT") else if (Misc::StringUtils::ciEqual(align, "left"))
mTextStyle.mTextAlign = MyGUI::Align::Left; mTextStyle.mTextAlign = MyGUI::Align::Left;
} }
void BookFormatter::parseFont(std::string tag) void BookFormatter::handleFont(const BookTextParser::Attributes & attr)
{ {
if (tag.find("COLOR=") != std::string::npos) if (attr.find("color") != attr.end())
{ {
int color_start = tag.find("COLOR=")+7;
int color; int color;
std::stringstream ss; std::stringstream ss;
ss << tag.substr(color_start, tag.find('"', color_start)-color_start); ss << attr.at("color");
ss >> std::hex >> color; ss >> std::hex >> color;
mTextStyle.mColour = MyGUI::Colour( mTextStyle.mColour = MyGUI::Colour(
@ -151,23 +272,22 @@ namespace MWGui
(color>>8 & 0xFF) / 255.f, (color>>8 & 0xFF) / 255.f,
(color & 0xFF) / 255.f); (color & 0xFF) / 255.f);
} }
if (tag.find("FACE=") != std::string::npos) if (attr.find("face") != attr.end())
{ {
int face_start = tag.find("FACE=")+6; std::string face = attr.at("face");
std::string face = tag.substr(face_start, tag.find('"', face_start)-face_start);
if (face != "Magic Cards") if (face != "Magic Cards")
mTextStyle.mFont = face; mTextStyle.mFont = face;
} }
if (tag.find("SIZE=") != std::string::npos) if (attr.find("size") != attr.end())
{ {
/// \todo /// \todo
} }
} }
/* GraphicElement */ /* GraphicElement */
GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style) GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag)
: mParent(parent), mPaginator(pag), mStyle(style) : mParent(parent), mPaginator(pag)
{ {
} }
@ -189,22 +309,11 @@ namespace MWGui
return mPaginator.getStartTop() + mPaginator.getPageHeight(); return mPaginator.getStartTop() + mPaginator.getPageHeight();
} }
int GraphicElement::currentFontHeight() const
{
std::string fontName(mStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mStyle.mFont);
return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight();
}
float GraphicElement::widthForCharGlyph(MyGUI::Char unicodeChar) const
{
std::string fontName(mStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mStyle.mFont);
return MyGUI::FontManager::getInstance().getByName(fontName)
->getGlyphInfo(unicodeChar)->width;
}
/* TextElement */ /* TextElement */
TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & text) TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag,
: GraphicElement(parent, pag, style) const TextStyle & style, const std::string & text)
: GraphicElement(parent, pag),
mStyle(style)
{ {
MyGUI::EditBox* box = parent->createWidget<MyGUI::EditBox>("NormalText", MyGUI::EditBox* box = parent->createWidget<MyGUI::EditBox>("NormalText",
MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top,
@ -222,6 +331,12 @@ namespace MWGui
mEditBox = box; mEditBox = box;
} }
int TextElement::currentFontHeight() const
{
std::string fontName(mStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mStyle.mFont);
return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight();
}
int TextElement::getHeight() int TextElement::getHeight()
{ {
return mEditBox->getTextSize().height; return mEditBox->getTextSize().height;
@ -250,19 +365,11 @@ namespace MWGui
} }
/* ImageElement */ /* ImageElement */
ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & tag) ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag,
: GraphicElement(parent, pag, style), const std::string & src, int width, int height)
mImageHeight(0) : GraphicElement(parent, pag),
mImageHeight(height)
{ {
int src_start = tag.find("SRC=")+5;
std::string src = tag.substr(src_start, tag.find('"', src_start)-src_start);
int width_start = tag.find("WIDTH=")+7;
int width = boost::lexical_cast<int>(tag.substr(width_start, tag.find('"', width_start)-width_start));
int height_start = tag.find("HEIGHT=")+8;
mImageHeight = boost::lexical_cast<int>(tag.substr(height_start, tag.find('"', height_start)-height_start));
mImageBox = parent->createWidget<MyGUI::ImageBox> ("ImageBox", mImageBox = parent->createWidget<MyGUI::ImageBox> ("ImageBox",
MyGUI::IntCoord(0, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, MyGUI::IntCoord(0, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,
parent->getName() + boost::lexical_cast<std::string>(parent->getChildCount())); parent->getName() + boost::lexical_cast<std::string>(parent->getChildCount()));
@ -279,6 +386,9 @@ namespace MWGui
int ImageElement::pageSplit() int ImageElement::pageSplit()
{ {
// if the image is larger than the page, fall back to the default pageSplit implementation
if (mImageHeight > mPaginator.getPageHeight())
return GraphicElement::pageSplit();
return mPaginator.getCurrentTop(); return mPaginator.getCurrentTop();
} }
} }

@ -2,6 +2,7 @@
#define MWGUI_FORMATTING_H #define MWGUI_FORMATTING_H
#include <MyGUI.h> #include <MyGUI.h>
#include <map>
namespace MWGui namespace MWGui
{ {
@ -23,6 +24,44 @@ namespace MWGui
MyGUI::Align mTextAlign; MyGUI::Align mTextAlign;
}; };
class BookTextParser
{
public:
typedef std::map<std::string, std::string> Attributes;
enum Events
{
Event_None = -2,
Event_EOF = -1,
Event_LastText = 0,
Event_BrTag,
Event_PTag,
Event_ImgTag,
Event_DivTag,
Event_FontTag
};
BookTextParser(const std::string & text);
void registerTag(const std::string & tag, Events type);
std::string getReadyText();
Events next();
void flushBuffer();
const Attributes & getAttributes() const;
void parseTag(std::string tag);
private:
int mIndex;
std::string mText;
std::string mReadyText;
bool mIgnoreNewlineTags;
bool mIgnoreLineEndings;
Attributes mAttributes;
std::string mTag;
std::map<std::string, Events> mTagTypes;
std::string mBuffer;
};
class Paginator class Paginator
{ {
public: public:
@ -62,12 +101,13 @@ namespace MWGui
class BookFormatter class BookFormatter
{ {
public: public:
Paginator::Pages markupToWidget(MyGUI::Widget * parent, std::string utf8Text, const int pageWidth, const int pageHeight); Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight);
Paginator::Pages markupToWidget(MyGUI::Widget * parent, std::string utf8Text); Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup);
protected: protected:
void parseDiv(std::string tag); void handleImg(const BookTextParser::Attributes & attr);
void parseFont(std::string tag); void handleDiv(const BookTextParser::Attributes & attr);
void handleFont(const BookTextParser::Attributes & attr);
private: private:
TextStyle mTextStyle; TextStyle mTextStyle;
}; };
@ -75,18 +115,14 @@ namespace MWGui
class GraphicElement class GraphicElement
{ {
public: public:
GraphicElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style); GraphicElement(MyGUI::Widget * parent, Paginator & pag);
virtual int getHeight() = 0; virtual int getHeight() = 0;
virtual void paginate(); virtual void paginate();
virtual int pageSplit(); virtual int pageSplit();
protected: protected:
int currentFontHeight() const;
float widthForCharGlyph(MyGUI::Char unicodeChar) const;
MyGUI::Widget * mParent; MyGUI::Widget * mParent;
Paginator & mPaginator; Paginator & mPaginator;
TextStyle mStyle;
}; };
class TextElement : public GraphicElement class TextElement : public GraphicElement
@ -96,13 +132,15 @@ namespace MWGui
virtual int getHeight(); virtual int getHeight();
virtual int pageSplit(); virtual int pageSplit();
private: private:
int currentFontHeight() const;
TextStyle mStyle;
MyGUI::EditBox * mEditBox; MyGUI::EditBox * mEditBox;
}; };
class ImageElement : public GraphicElement class ImageElement : public GraphicElement
{ {
public: public:
ImageElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & tag); ImageElement(MyGUI::Widget * parent, Paginator & pag, const std::string & src, int width, int height);
virtual int getHeight(); virtual int getHeight();
virtual int pageSplit(); virtual int pageSplit();

@ -41,8 +41,8 @@
<Property key="TextColour" value="0 0 0"/> <Property key="TextColour" value="0 0 0"/>
</Widget> </Widget>
<Widget type="Widget" skin="" position="30 15 240 324" name="LeftPage"/> <Widget type="Widget" skin="" position="30 15 240 328" name="LeftPage"/>
<Widget type="Widget" skin="" position="300 15 240 324" name="RightPage"/> <Widget type="Widget" skin="" position="300 15 240 328" name="RightPage"/>
</Widget> </Widget>
</Widget> </Widget>
</Widget> </Widget>

Loading…
Cancel
Save