From c362ec0f95ebacd7160789b3adb9630724261b20 Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Sun, 21 Sep 2014 19:47:52 +0200 Subject: [PATCH] Book formatting: Handle line endings as per vanilla, fix tall images causing infinite loop, cleanup --- apps/openmw/mwgui/formatting.cpp | 320 +++++++++++++++++++++---------- apps/openmw/mwgui/formatting.hpp | 58 +++++- files/mygui/openmw_book.layout | 4 +- 3 files changed, 265 insertions(+), 117 deletions(-) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 67ef59236..a7aee1bc9 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwscript/interpretercontext.hpp" @@ -14,39 +15,167 @@ #include -namespace -{ - Ogre::UTFString::unicode_char unicodeCharFromChar(char ch) - { - std::string s; - s += ch; - Ogre::UTFString string(s); - return string.getChar(0); - } -} - namespace MWGui { 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 - 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", ""); - boost::algorithm::replace_all(utf8Text, "\r", ""); - boost::algorithm::replace_all(utf8Text, "
", "\n"); - boost::algorithm::replace_all(utf8Text, "

", "\n\n"); + std::string key = tag.substr(0, sepPos); + Misc::StringUtils::toLower(key); + tag.erase(0, sepPos+1); - UTFString text(utf8Text); - UTFString plainText; + std::string value; - const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<'); - const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\n'); + if (tag.empty()) + 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); while (parent->getChildCount()) @@ -57,55 +186,49 @@ namespace MWGui MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); paper->setNeedMouseFocus(false); - bool ignoreNewlines = true; - for (size_t index = 0; index < text.size(); ++index) + BookTextParser parser(markup); + BookTextParser::Events event; + while ((event = parser.next()) != BookTextParser::Event_EOF) { - const UTFString::unicode_char ch = text.getChar(index); - if (!plainText.empty() && (ch == LEFT_ANGLE || index == text.size() - 1)) + if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) + continue; + + std::string plainText = parser.getReadyText(); + if (!plainText.empty()) { // 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); - TextElement elem(paper, pag, mTextStyle, plainText.asUTF8()); + TextElement elem(paper, pag, mTextStyle, plainText); elem.paginate(); - - plainText.clear(); } - if (ch == LEFT_ANGLE) + switch (event) { - const size_t tagStart = index + 1; - 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")) + case BookTextParser::Event_ImgTag: { - ImageElement elem(paper, pag, mTextStyle, tag); - elem.paginate(); + const BookTextParser::Attributes & attr = parser.getAttributes(); - ignoreNewlines = false; - } - else if (boost::algorithm::starts_with(tag, "FONT")) - { - parseFont(tag); - } - else if (boost::algorithm::starts_with(tag, "DIV")) - { - parseDiv(tag); - } + if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) + continue; - index = tagEnd; - } - else - { - if (!ignoreNewlines || ch != NEWLINE) - { - plainText.push_back(ch); - ignoreNewlines = false; + std::string src = attr.at("src"); + int width = boost::lexical_cast(attr.at("width")); + int height = boost::lexical_cast(attr.at("height")); + + ImageElement elem(paper, pag, src, width, height); + elem.paginate(); + break; } + 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(); } - 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; - int align_start = tag.find("ALIGN=")+7; - std::string align = tag.substr(align_start, tag.find('"', align_start)-align_start); - if (align == "CENTER") + std::string align = attr.at("align"); + + if (Misc::StringUtils::ciEqual(align, "center")) mTextStyle.mTextAlign = MyGUI::Align::HCenter; - else if (align == "LEFT") + else if (Misc::StringUtils::ciEqual(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; std::stringstream ss; - ss << tag.substr(color_start, tag.find('"', color_start)-color_start); + ss << attr.at("color"); ss >> std::hex >> color; mTextStyle.mColour = MyGUI::Colour( @@ -151,23 +272,22 @@ namespace MWGui (color>>8 & 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 = tag.substr(face_start, tag.find('"', face_start)-face_start); + std::string face = attr.at("face"); if (face != "Magic Cards") mTextStyle.mFont = face; } - if (tag.find("SIZE=") != std::string::npos) + if (attr.find("size") != attr.end()) { /// \todo } } /* GraphicElement */ - GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style) - : mParent(parent), mPaginator(pag), mStyle(style) + GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag) + : mParent(parent), mPaginator(pag) { } @@ -189,22 +309,11 @@ namespace MWGui 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(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & text) - : GraphicElement(parent, pag, style) + TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag, + const TextStyle & style, const std::string & text) + : GraphicElement(parent, pag), + mStyle(style) { MyGUI::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, @@ -222,6 +331,12 @@ namespace MWGui 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() { return mEditBox->getTextSize().height; @@ -250,19 +365,11 @@ namespace MWGui } /* ImageElement */ - ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style, const std::string & tag) - : GraphicElement(parent, pag, style), - mImageHeight(0) + ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, + const std::string & src, int width, int height) + : 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(tag.substr(width_start, tag.find('"', width_start)-width_start)); - - int height_start = tag.find("HEIGHT=")+8; - mImageHeight = boost::lexical_cast(tag.substr(height_start, tag.find('"', height_start)-height_start)); - mImageBox = parent->createWidget ("ImageBox", MyGUI::IntCoord(0, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + boost::lexical_cast(parent->getChildCount())); @@ -279,6 +386,9 @@ namespace MWGui 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(); } } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index b0a8bce0f..545c1f31d 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -2,6 +2,7 @@ #define MWGUI_FORMATTING_H #include +#include namespace MWGui { @@ -23,6 +24,44 @@ namespace MWGui MyGUI::Align mTextAlign; }; + class BookTextParser + { + public: + typedef std::map 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 mTagTypes; + std::string mBuffer; + }; + class Paginator { public: @@ -62,12 +101,13 @@ namespace MWGui class BookFormatter { public: - Paginator::Pages markupToWidget(MyGUI::Widget * parent, std::string utf8Text, 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, const int pageWidth, const int pageHeight); + Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup); protected: - void parseDiv(std::string tag); - void parseFont(std::string tag); + void handleImg(const BookTextParser::Attributes & attr); + void handleDiv(const BookTextParser::Attributes & attr); + void handleFont(const BookTextParser::Attributes & attr); private: TextStyle mTextStyle; }; @@ -75,18 +115,14 @@ namespace MWGui class GraphicElement { public: - GraphicElement(MyGUI::Widget * parent, Paginator & pag, const TextStyle & style); + GraphicElement(MyGUI::Widget * parent, Paginator & pag); virtual int getHeight() = 0; virtual void paginate(); virtual int pageSplit(); protected: - int currentFontHeight() const; - float widthForCharGlyph(MyGUI::Char unicodeChar) const; - MyGUI::Widget * mParent; Paginator & mPaginator; - TextStyle mStyle; }; class TextElement : public GraphicElement @@ -96,13 +132,15 @@ namespace MWGui virtual int getHeight(); virtual int pageSplit(); private: + int currentFontHeight() const; + TextStyle mStyle; MyGUI::EditBox * mEditBox; }; class ImageElement : public GraphicElement { 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 pageSplit(); diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index f91cd696e..3b1cfea64 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -41,8 +41,8 @@ - - + +