GitHub PullRequestReviewCommentEvent at 2014-09-22 09:23:26
Repository: openmw
Actor: scrawl
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/OpenMW/openmw/pulls/comments/17839221","id":17839221,"diff_hunk":"@@ -2,401 +2,398 @@\n \n #include <components/interpreter/defines.hpp>\n #include <components/misc/resourcehelpers.hpp>\n+#include <components/misc/stringops.hpp>\n \n #include \"../mwscript/interpretercontext.hpp\"\n \n #include <boost/algorithm/string/replace.hpp>\n #include <boost/algorithm/string/predicate.hpp>\n-#include <boost/algorithm/string/trim.hpp>\n #include <boost/lexical_cast.hpp>\n-#include <boost/range/adaptor/filtered.hpp>\n-#include <boost/range/algorithm/copy.hpp>\n-#include <OgreUTFString.h>\n \n+#include <OgreUTFString.h>\n #include <OgreResourceGroupManager.h>\n \n-namespace\n+#include <MyGUI_EditText.h>\n+\n+namespace MWGui\n {\n-    int convertFromHex(std::string hex)\n+    namespace Formatting\n     {\n-        int value = 0;\n-\n-        int a = 0;\n-        int b = hex.length() - 1;\n-        for (; b >= 0; a++, b--)\n+        /* BookTextParser */\n+        BookTextParser::BookTextParser(const std::string & text)\n+            : mIndex(-1), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true)\n         {\n-            if (hex[b] >= '0' && hex[b] <= '9')\n-            {\n-                value += (hex[b] - '0') * (1 << (a * 4));\n-            }\n-            else\n-            {\n-                switch (hex[b])\n-                {\n-                    case 'A':\n-                    case 'a':\n-                        value += 10 * (1 << (a * 4));\n-                        break;\n+            MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor\n+            mText = Interpreter::fixDefinesBook(mText, interpreterContext);\n \n-                    case 'B':\n-                    case 'b':\n-                        value += 11 * (1 << (a * 4));\n-                        break;\n+            boost::algorithm::replace_all(mText, \"\\r\", \"\");\n \n-                    case 'C':\n-                    case 'c':\n-                        value += 12 * (1 << (a * 4));\n-                        break;\n+            registerTag(\"br\", Event_BrTag);\n+            registerTag(\"p\", Event_PTag);\n+            registerTag(\"img\", Event_ImgTag);\n+            registerTag(\"div\", Event_DivTag);\n+            registerTag(\"font\", Event_FontTag);\n+        }\n \n-                    case 'D':\n-                    case 'd':\n-                        value += 13 * (1 << (a * 4));\n-                        break;\n+        void BookTextParser::registerTag(const std::string & tag, BookTextParser::Events type)\n+        {\n+            mTagTypes[tag] = type;\n+        }\n \n-                    case 'E':\n-                    case 'e':\n-                        value += 14 * (1 << (a * 4));\n-                        break;\n+        std::string BookTextParser::getReadyText()\n+        {\n+            return mReadyText;\n+        }\n \n-                    case 'F':\n-                    case 'f':\n-                        value += 15 * (1 << (a * 4));\n-                        break;\n+        BookTextParser::Events BookTextParser::next()\n+        {\n+            while (1)\n+            {\n+                ++mIndex;\n \n-                    default:\n-                        throw std::runtime_error(\"invalid character in hex number\");\n-                        break;\n+                if (mIndex >= mText.size())\n+                {\n+                    flushBuffer();\n+                    return Event_EOF;\n                 }\n-            }\n-        }\n \n-        return value;\n-    }\n-\n-    Ogre::UTFString::unicode_char unicodeCharFromChar(char ch)\n-    {\n-        std::string s;\n-        s += ch;\n-        Ogre::UTFString string(s);\n-        return string.getChar(0);\n-    }\n-    \n-    bool is_not_empty(const std::string& s) {\n-        std::string temp = s;\n-        boost::algorithm::trim(temp);\n-        return !temp.empty();\n-    }\n-}\n+                char ch = mText[mIndex];\n+                if (ch == '<')\n+                {\n+                    const size_t tagStart = mIndex + 1;\n+                    const size_t tagEnd = mText.find('>', tagStart);\n+                    if (tagEnd == std::string::npos)\n+                        throw std::runtime_error(\"BookTextParser Error: Tag is not terminated\");\n+                    parseTag(mText.substr(tagStart, tagEnd - tagStart));\n+                    mIndex = tagEnd;\n \n-namespace MWGui\n-{\n+                    if (mTagTypes.find(mTag) != mTagTypes.end())\n+                    {\n+                        Events type = mTagTypes.at(mTag);\n+\n+                        if (type == Event_BrTag || type == Event_PTag)\n+                        {\n+                            if (!mIgnoreNewlineTags)\n+                            {\n+                                if (type == Event_BrTag)\n+                                    mBuffer.push_back('\\n');\n+                                else\n+                                {\n+                                    mBuffer.append(\"\\n\\n\");\n+                                }\n+                            }\n+                            mIgnoreLineEndings = true;\n+                        }\n+                        else\n+                            flushBuffer();\n \n-    std::vector<std::string> BookTextParser::split(std::string utf8Text, const int width, const int height)\n-    {\n-        using Ogre::UTFString;\n-        std::vector<std::string> result;\n+                        if (type == Event_ImgTag)\n+                        {\n+                            mIgnoreLineEndings = false;\n+                            mIgnoreNewlineTags = false;\n+                        }\n \n-        MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor\n-        utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext);\n+                        return type;\n+                    }\n+                }\n+                else\n+                {\n+                    if (!mIgnoreLineEndings || ch != '\\n')\n+                    {\n+                        mBuffer.push_back(ch);\n+                        mIgnoreLineEndings = false;\n+                        mIgnoreNewlineTags = false;\n+                    }\n+                }\n \n-        boost::algorithm::replace_all(utf8Text, \"\\n\", \"\");\n-        boost::algorithm::replace_all(utf8Text, \"\\r\", \"\");\n-        boost::algorithm::replace_all(utf8Text, \"<BR>\", \"\\n\");\n-        boost::algorithm::replace_all(utf8Text, \"<P>\", \"\\n\\n\");\n \n-        UTFString text(utf8Text);\n-        const int spacing = 48;\n+            }\n+        }\n \n-        const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<');\n-        const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\\n');\n-        const UTFString::unicode_char SPACE = unicodeCharFromChar(' ');\n+        void BookTextParser::flushBuffer()\n+        {\n+            mReadyText = mBuffer;\n+            mBuffer.clear();\n+        }\n \n-        while (!text.empty())\n+        const BookTextParser::Attributes & BookTextParser::getAttributes() const\n         {\n-            // read in characters until we have exceeded the size, or run out of text\n-            int currentWidth = 0;\n-            int currentHeight = 0;\n+            return mAttributes;\n+        }\n \n-            size_t currentWordStart = 0;\n-            size_t index = 0;\n-            \n-            {\n-                std::string texToTrim = text.asUTF8();\n-                boost::algorithm::trim( texToTrim );\n-                text = UTFString(texToTrim);\n-            }\n-            \n-            \n-            while (currentHeight <= height - spacing && index < text.size())\n+        void BookTextParser::parseTag(std::string tag)\n+        {\n+            size_t tagNameEndPos = tag.find(' ');\n+            mTag = tag.substr(0, tagNameEndPos);\n+            Misc::StringUtils::toLower(mTag);\n+            mAttributes.clear();\n+            if (tagNameEndPos == std::string::npos)\n+                return;\n+            tag.erase(0, tagNameEndPos+1);\n+\n+            while (!tag.empty())\n             {\n-                const UTFString::unicode_char ch = text.getChar(index);\n-                if (ch == LEFT_ANGLE)\n-                {\n-                    const size_t tagStart = index + 1;\n-                    const size_t tagEnd = text.find('>', tagStart);\n-                    if (tagEnd == UTFString::npos)\n-                        throw std::runtime_error(\"BookTextParser Error: Tag is not terminated\");\n-                    const std::string tag = text.substr(tagStart, tagEnd - tagStart).asUTF8();\n+                size_t sepPos = tag.find('=');\n+                if (sepPos == std::string::npos)\n+                    return;\n \n-                    if (boost::algorithm::starts_with(tag, \"IMG\"))\n-                    {\n-                        const int h = mHeight;\n-                        parseImage(tag, false);\n-                        currentHeight += (mHeight - h);\n-                        currentWidth = 0;\n-                    }\n-                    else if (boost::algorithm::starts_with(tag, \"FONT\"))\n-                    {\n-                        parseFont(tag);\n-                        if (currentWidth != 0) {\n-                            currentHeight += currentFontHeight();\n-                        }\n-                        currentWidth = 0;\n-                    }\n-                    else if (boost::algorithm::starts_with(tag, \"DIV\"))\n-                    {\n-                        parseDiv(tag);\n-                        if (currentWidth != 0) {\n-                            currentHeight += currentFontHeight();\n-                            currentWidth = 0;\n-                        }\n-                    }\n-                    index = tagEnd;\n-                }\n-                else if (ch == NEWLINE)\n-                {\n-                    currentHeight += currentFontHeight();\n-                    currentWidth = 0;\n-                    currentWordStart = index;\n-                }\n-                else if (ch == SPACE)\n+                std::string key = tag.substr(0, sepPos);\n+                Misc::StringUtils::toLower(key);\n+                tag.erase(0, sepPos+1);\n+\n+                std::string value;\n+\n+                if (tag.empty())\n+                    return;\n+\n+                if (tag[0] == '\"')\n                 {\n-                    currentWidth += 3; // keep this in sync with the font's SpaceWidth property\n-                    currentWordStart = index;\n+                    size_t quoteEndPos = tag.find('\"', 1);\n+                    if (quoteEndPos == std::string::npos)\n+                        throw std::runtime_error(\"BookTextParser Error: Missing end quote in tag\");\n+                    value = tag.substr(1, quoteEndPos-1);\n+                    tag.erase(0, quoteEndPos+2);\n                 }\n                 else\n                 {\n-                    currentWidth += widthForCharGlyph(ch);\n+                    size_t valEndPos = tag.find(' ');\n+                    if (valEndPos == std::string::npos)\n+                    {\n+                        value = tag;\n+                        tag.erase();\n+                    }\n+                    else\n+                    {\n+                        value = tag.substr(0, valEndPos);\n+                        tag.erase(0, valEndPos+1);\n+                    }\n                 }\n \n-                if (currentWidth > width)\n-                {\n-                    currentHeight += currentFontHeight();\n-                    currentWidth = 0;\n-                    // add size of the current word\n-                    UTFString word = text.substr(currentWordStart, index - currentWordStart);\n-                    for (UTFString::const_iterator it = word.begin(), end = word.end(); it != end; ++it)\n-                        currentWidth += widthForCharGlyph(it.getCharacter());\n-                }\n-                index += UTFString::_utf16_char_length(ch);\n+                mAttributes[key] = value;\n             }\n-            const size_t pageEnd = (currentHeight > height - spacing && currentWordStart != 0)\n-                    ? currentWordStart : index;\n-\n-            result.push_back(text.substr(0, pageEnd).asUTF8());\n-            text.erase(0, pageEnd);\n         }\n-        \n-        std::vector<std::string> nonEmptyPages;\n-        boost::copy(result | boost::adaptors::filtered(is_not_empty), std::back_inserter(nonEmptyPages));\n-        return nonEmptyPages;\n-    }\n \n-    float BookTextParser::widthForCharGlyph(unsigned unicodeChar) const\n-    {\n-        std::string fontName(mTextStyle.mFont == \"Default\" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont);\n-        return MyGUI::FontManager::getInstance().getByName(fontName)\n-                ->getGlyphInfo(unicodeChar)->width;\n-    }\n+        /* BookFormatter */\n+        Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight)\n+        {\n+            Paginator pag(pageWidth, pageHeight);\n \n-    float BookTextParser::currentFontHeight() const\n-    {\n-        std::string fontName(mTextStyle.mFont == \"Default\" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont);\n-        return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight();\n-    }\n+            while (parent->getChildCount())\n+            {\n+                MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0));\n+            }\n \n-    MyGUI::IntSize BookTextParser::parsePage(std::string text, MyGUI::Widget* parent, const int width)\n-    {\n-        MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor\n-        text = Interpreter::fixDefinesBook(text, interpreterContext);\n+            MyGUI::Widget * paper = parent->createWidget<MyGUI::Widget>(\"Widget\", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top);\n+            paper->setNeedMouseFocus(false);\n \n-        mParent = parent;\n-        mWidth = width;\n-        mHeight = 0;\n+            BookTextParser parser(markup);\n+            BookTextParser::Events event;\n+            for (;;)\n+            {\n+                event = parser.next();\n+                if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag)\n+                    continue;\n \n-        assert(mParent);\n-        while (mParent->getChildCount())\n-        {\n-            MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0));\n-        }\n+                std::string plainText = parser.getReadyText();\n+                if (!plainText.empty())\n+                {\n+                    // if there's a newline at the end of the box caption, remove it\n+                    if (plainText[plainText.size()-1] == '\\n')\n+                        plainText.erase(plainText.end()-1);\n \n-        // remove trailing \"\n-        if (text[text.size()-1] == '\\\"')\n-            text.erase(text.size()-1);\n+                    TextElement elem(paper, pag, mTextStyle, plainText);\n+                    elem.paginate();\n+                }\n \n-        parseSubText(text);\n-        return MyGUI::IntSize(mWidth, mHeight);\n-    }\n-    \n-    MyGUI::IntSize BookTextParser::parseScroll(std::string text, MyGUI::Widget* parent, const int width)\n-    {\n-        MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor\n-        text = Interpreter::fixDefinesBook(text, interpreterContext);\n+                if (event == BookTextParser::Event_EOF)\n+                    break;\n \n-        mParent = parent;\n-        mWidth = width;\n-        mHeight = 0;\n+                switch (event)\n+                {\n+                    case BookTextParser::Event_ImgTag:\n+                    {\n+                        const BookTextParser::Attributes & attr = parser.getAttributes();\n \n-        assert(mParent);\n-        while (mParent->getChildCount())\n-        {\n-            MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0));\n-        }\n+                        if (attr.find(\"src\") == attr.end() || attr.find(\"width\") == attr.end() || attr.find(\"height\") == attr.end())\n+                            continue;\n \n-        boost::algorithm::replace_all(text, \"<BR>\", \"\\n\");\n-        boost::algorithm::replace_all(text, \"<P>\", \"\\n\\n\");\n-        boost::algorithm::trim_left(text);\n+                        std::string src = attr.at(\"src\");\n+                        int width = boost::lexical_cast<int>(attr.at(\"width\"));\n+                        int height = boost::lexical_cast<int>(attr.at(\"height\"));\n \n-        // remove trailing \"\n-        if (text[text.size()-1] == '\\\"')\n-            text.erase(text.size()-1);\n+                        ImageElement elem(paper, pag, src, width, height);\n+                        elem.paginate();\n+                        break;\n+                    }\n+                    case BookTextParser::Event_FontTag:\n+                        handleFont(parser.getAttributes());\n+                        break;\n+                    case BookTextParser::Event_DivTag:\n+                        handleDiv(parser.getAttributes());\n+                        break;\n+                    default:\n+                        break;\n+                }\n+            }\n \n-        parseSubText(text);\n-        return MyGUI::IntSize(mWidth, mHeight);\n-    }\n-    \n+            // insert last page\n+            pag << Paginator::Page(pag.getStartTop(), pag.getStartTop() + pag.getPageHeight());\n \n-    void BookTextParser::parseImage(std::string tag, bool createWidget)\n-    {\n-        int src_start = tag.find(\"SRC=\")+5;\n+            paper->setSize(paper->getWidth(), pag.getCurrentTop());\n \n-        int width_start = tag.find(\"WIDTH=\")+7;\n-        int width = boost::lexical_cast<int>(tag.substr(width_start, tag.find('\"', width_start)-width_start));\n+            return pag.getPages();\n+        }\n \n-        int height_start = tag.find(\"HEIGHT=\")+8;\n-        int height = boost::lexical_cast<int>(tag.substr(height_start, tag.find('\"', height_start)-height_start));\n+        Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup)\n+        {\n+            return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight());\n+        }\n \n-        if (createWidget)\n+        void BookFormatter::handleDiv(const BookTextParser::Attributes & attr)\n         {\n-            MyGUI::ImageBox* box = mParent->createWidget<MyGUI::ImageBox> (\"ImageBox\",\n-                MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top,\n-                mParent->getName() + boost::lexical_cast<std::string>(mParent->getChildCount()));\n+            if (attr.find(\"align\") == attr.end())\n+                return;\n \n-            std::string image = Misc::ResourceHelpers::correctBookartPath(tag.substr(src_start, tag.find('\"', src_start)-src_start), width, height);\n-            box->setImageTexture(image);\n-            box->setProperty(\"NeedMouse\", \"false\");\n+            std::string align = attr.at(\"align\");\n+\n+            if (Misc::StringUtils::ciEqual(align, \"center\"))\n+                mTextStyle.mTextAlign = MyGUI::Align::HCenter;\n+            else if (Misc::StringUtils::ciEqual(align, \"left\"))\n+                mTextStyle.mTextAlign = MyGUI::Align::Left;\n         }\n \n-        mWidth = std::max(mWidth, width);\n-        mHeight += height;\n-    }\n+        void BookFormatter::handleFont(const BookTextParser::Attributes & attr)\n+        {\n+            if (attr.find(\"color\") != attr.end())\n+            {\n+                int color;\n+                std::stringstream ss;\n+                ss << attr.at(\"color\");\n+                ss >> std::hex >> color;\n+\n+                mTextStyle.mColour = MyGUI::Colour(\n+                    (color>>16 & 0xFF) / 255.f,\n+                    (color>>8 & 0xFF) / 255.f,\n+                    (color & 0xFF) / 255.f);\n+            }\n+            if (attr.find(\"face\") != attr.end())\n+            {\n+                std::string face = attr.at(\"face\");\n \n-    void BookTextParser::parseDiv(std::string tag)\n-    {\n-        if (tag.find(\"ALIGN=\") == std::string::npos)\n-            return;\n-\n-        int align_start = tag.find(\"ALIGN=\")+7;\n-        std::string align = tag.substr(align_start, tag.find('\"', align_start)-align_start);\n-        if (align == \"CENTER\")\n-            mTextStyle.mTextAlign = MyGUI::Align::HCenter;\n-        else if (align == \"LEFT\")\n-            mTextStyle.mTextAlign = MyGUI::Align::Left;\n-    }\n+                if (face != \"Magic Cards\")\n+                    mTextStyle.mFont = face;\n+            }\n+            if (attr.find(\"size\") != attr.end())\n+            {\n+                /// \\todo\n+            }\n+        }\n \n-    void BookTextParser::parseFont(std::string tag)\n-    {\n-        if (tag.find(\"COLOR=\") != std::string::npos)\n+        /* GraphicElement */\n+        GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag)\n+            : mParent(parent), mPaginator(pag)\n         {\n-            int color_start = tag.find(\"COLOR=\")+7;\n-            std::string color = tag.substr(color_start, tag.find('\"', color_start)-color_start);\n+        }\n \n-            mTextStyle.mColour = MyGUI::Colour(\n-                convertFromHex(color.substr(0, 2))/255.0,\n-                convertFromHex(color.substr(2, 2))/255.0,\n-                convertFromHex(color.substr(4, 2))/255.0);\n+        void GraphicElement::paginate()\n+        {\n+            int newTop = mPaginator.getCurrentTop() + getHeight();\n+            while (newTop-mPaginator.getStartTop() > mPaginator.getPageHeight())\n+            {\n+                int newStartTop = pageSplit();\n+                mPaginator << Paginator::Page(mPaginator.getStartTop(), newStartTop);\n+                mPaginator.setStartTop(newStartTop);\n+            }\n+\n+            mPaginator.modifyCurrentTop(getHeight());\n         }\n-        if (tag.find(\"FACE=\") != std::string::npos)\n+\n+        int GraphicElement::pageSplit()\n         {\n-            int face_start = tag.find(\"FACE=\")+6;\n-            std::string face = tag.substr(face_start, tag.find('\"', face_start)-face_start);\n+            return mPaginator.getStartTop() + mPaginator.getPageHeight();\n+        }\n \n-            if (face != \"Magic Cards\")\n-                mTextStyle.mFont = face;\n+        /* TextElement */\n+        TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag,\n+                                 const TextStyle & style, const std::string & text)\n+            : GraphicElement(parent, pag),\n+              mStyle(style)\n+        {\n+            MyGUI::EditBox* box = parent->createWidget<MyGUI::EditBox>(\"NormalText\",\n+                MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top,\n+                parent->getName() + boost::lexical_cast<std::string>(parent->getChildCount()));\n+            box->setProperty(\"Static\", \"true\");\n+            box->setProperty(\"MultiLine\", \"true\");\n+            box->setProperty(\"WordWrap\", \"true\");\n+            box->setProperty(\"NeedMouse\", \"false\");\n+            box->setMaxTextLength(text.size());\n+            box->setTextAlign(mStyle.mTextAlign);\n+            box->setTextColour(mStyle.mColour);\n+            box->setFontName(mStyle.mFont);\n+            box->setCaption(MyGUI::TextIterator::toTagsString(text));\n+            box->setSize(box->getSize().width, box->getTextSize().height);\n+            mEditBox = box;\n         }\n-        if (tag.find(\"SIZE=\") != std::string::npos)\n+\n+        int TextElement::currentFontHeight() const\n         {\n-            /// \\todo\n+            std::string fontName(mStyle.mFont == \"Default\" ? MyGUI::FontManager::getInstance().getDefaultFont() : mStyle.mFont);\n+            return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight();\n         }\n-    }\n \n-    void BookTextParser::parseSubText(std::string text)\n-    {\n-        if (text[0] == '<')\n+        int TextElement::getHeight()\n         {\n-            const size_t tagStart = 1;\n-            const size_t tagEnd = text.find('>', tagStart);\n-            if (tagEnd == std::string::npos)\n-                throw std::runtime_error(\"BookTextParser Error: Tag is not terminated\");\n-            const std::string tag = text.substr(tagStart, tagEnd - tagStart);\n-\n-            if (boost::algorithm::starts_with(tag, \"IMG\"))\n-                parseImage(tag);\n-            if (boost::algorithm::starts_with(tag, \"FONT\"))\n-                parseFont(tag);\n-            if (boost::algorithm::starts_with(tag, \"DIV\"))\n-                parseDiv(tag);\n-\n-            text.erase(0, tagEnd + 1);\n+            return mEditBox->getTextSize().height;\n         }\n \n-        size_t tagStart = std::string::npos;\n-        std::string realText; // real text, without tags\n-        for (size_t i = 0; i<text.size(); ++i)\n+        int TextElement::pageSplit()\n         {\n-            char c = text[i];\n-            if (c == '<')\n+            // split lines\n+            const int lineHeight = currentFontHeight();\n+            unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()) / lineHeight;\n+            int ret = mPaginator.getCurrentTop() + lastLine * lineHeight;\n+\n+            // first empty lines that would go to the next page should be ignored\n+            // unfortunately, getLineInfo method won't be available until 3.2.2\n+#if (MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3, 2, 2))\n+            const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType<MyGUI::EditText>()->getLineInfo();\n+            for (unsigned int i = lastLine; i < lines.size(); ++i)\n             {\n-                if ((i + 1 < text.size()) && text[i+1] == '/') // ignore closing tags\n-                {\n-                    while (c != '>')\n-                    {\n-                        if (i >= text.size())\n-                            throw std::runtime_error(\"BookTextParser Error: Tag is not terminated\");\n-                        ++i;\n-                        c = text[i];\n-                    }\n-                    continue;\n-                }\n+                if (lines[i].width == 0)\n+                    ret += lineHeight;\n                 else\n-                {\n-                    tagStart = i;\n                     break;\n-                }\n             }\n-            else\n-                realText += c;\n+#endif\n+            return ret;\n         }\n \n-        MyGUI::EditBox* box = mParent->createWidget<MyGUI::EditBox>(\"NormalText\",\n-            MyGUI::IntCoord(0, mHeight, mWidth, 24), MyGUI::Align::Left | MyGUI::Align::Top,\n-            mParent->getName() + boost::lexical_cast<std::string>(mParent->getChildCount()));\n-        box->setProperty(\"Static\", \"true\");\n-        box->setProperty(\"MultiLine\", \"true\");\n-        box->setProperty(\"WordWrap\", \"true\");\n-        box->setProperty(\"NeedMouse\", \"false\");\n-        box->setMaxTextLength(realText.size());\n-        box->setTextAlign(mTextStyle.mTextAlign);\n-        box->setTextColour(mTextStyle.mColour);\n-        box->setFontName(mTextStyle.mFont);\n-        box->setCaption(MyGUI::TextIterator::toTagsString(realText));\n-        box->setSize(box->getSize().width, box->getTextSize().height);\n-        mHeight += box->getTextSize().height;\n-\n-        if (tagStart != std::string::npos)\n+        /* ImageElement */\n+        ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag,\n+                                   const std::string & src, int width, int height)\n+            : GraphicElement(parent, pag),\n+              mImageHeight(height)\n         {\n-            parseSubText(text.substr(tagStart, text.size()));\n+            mImageBox = parent->createWidget<MyGUI::ImageBox> (\"ImageBox\",\n+                MyGUI::IntCoord(0, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,\n+                parent->getName() + boost::lexical_cast<std::string>(parent->getChildCount()));\n+\n+            std::string image = Misc::ResourceHelpers::correctBookartPath(src, width, mImageHeight);\n+            mImageBox->setImageTexture(image);\n+            mImageBox->setProperty(\"NeedMouse\", \"false\");\n         }\n-    }\n \n+        int ImageElement::getHeight()\n+        {\n+            return mImageHeight;\n+        }\n+\n+        int ImageElement::pageSplit()\n+        {\n+            // if the image is larger than the page, fall back to the default pageSplit implementation\n+            if (mImageHeight > mPaginator.getPageHeight())\n+                return GraphicElement::pageSplit();\n+            return mPaginator.getCurrentTop();","path":"apps/openmw/mwgui/formatting.cpp","position":709,"original_position":709,"commit_id":"51cd2678aeb094b1b50d417b8d189b0331b4ab56","original_commit_id":"51cd2678aeb094b1b50d417b8d189b0331b4ab56","user":{"login":"scrawl","id":720642,"avatar_url":"https://avatars.githubusercontent.com/u/720642?v=2","gravatar_id":"bc9ff596f281d951894e8cee1e9354e4","url":"https://api.github.com/users/scrawl","html_url":"https://github.com/scrawl","followers_url":"https://api.github.com/users/scrawl/followers","following_url":"https://api.github.com/users/scrawl/following{/other_user}","gists_url":"https://api.github.com/users/scrawl/gists{/gist_id}","starred_url":"https://api.github.com/users/scrawl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scrawl/subscriptions","organizations_url":"https://api.github.com/users/scrawl/orgs","repos_url":"https://api.github.com/users/scrawl/repos","events_url":"https://api.github.com/users/scrawl/events{/privacy}","received_events_url":"https://api.github.com/users/scrawl/received_events","type":"User","site_admin":false},"body":"I'm not sure if this line is working as expected. Shouldn't it take the image height into account in some way?","created_at":"2014-09-22T09:23:26Z","updated_at":"2014-09-22T09:23:26Z","html_url":"https://github.com/OpenMW/openmw/pull/282#discussion_r17839221","pull_request_url":"https://api.github.com/repos/OpenMW/openmw/pulls/282","_links":{"self":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/comments/17839221"},"html":{"href":"https://github.com/OpenMW/openmw/pull/282#discussion_r17839221"},"pull_request":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/282"}}},"pull_request":{"url":"https://api.github.com/repos/OpenMW/openmw/pulls/282","id":21527715,"html_url":"https://github.com/OpenMW/openmw/pull/282","diff_url":"https://github.com/OpenMW/openmw/pull/282.diff","patch_url":"https://github.com/OpenMW/openmw/pull/282.patch","issue_url":"https://api.github.com/repos/OpenMW/openmw/issues/282","number":282,"state":"open","locked":false,"title":"Rework book formatter (Fixes #1148)","user":{"login":"MiroslavR","id":7346770,"avatar_url":"https://avatars.githubusercontent.com/u/7346770?v=2","gravatar_id":"f08514e1f1532356434893744caf5dc7","url":"https://api.github.com/users/MiroslavR","html_url":"https://github.com/MiroslavR","followers_url":"https://api.github.com/users/MiroslavR/followers","following_url":"https://api.github.com/users/MiroslavR/following{/other_user}","gists_url":"https://api.github.com/users/MiroslavR/gists{/gist_id}","starred_url":"https://api.github.com/users/MiroslavR/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MiroslavR/subscriptions","organizations_url":"https://api.github.com/users/MiroslavR/orgs","repos_url":"https://api.github.com/users/MiroslavR/repos","events_url":"https://api.github.com/users/MiroslavR/events{/privacy}","received_events_url":"https://api.github.com/users/MiroslavR/received_events","type":"User","site_admin":false},"body":"Tested on quite a few books with positive results. Replicates the vanilla layouts as closely as possible, fixing a lot of bugs in the current formatter. The only minor problem would be that texts in vanilla are slightly shorter, causing that the word wrapping does not always match, which may or may not be a scaling bug in vanilla MW.\r\n\r\nNext thing is that when an editbox is split, all empty lines at the beginning of the next page should be removed. While I have implemented that, it requires the latest, not yet released, version of mygui which adds the EditText::getLineInfo method.","created_at":"2014-09-19T22:28:31Z","updated_at":"2014-09-22T09:23:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"03b5a5ebcfa2e09d302c05d41755e7995bc97f8e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/OpenMW/openmw/pulls/282/commits","review_comments_url":"https://api.github.com/repos/OpenMW/openmw/pulls/282/comments","review_comment_url":"https://api.github.com/repos/OpenMW/openmw/pulls/comments/{number}","comments_url":"https://api.github.com/repos/OpenMW/openmw/issues/282/comments","statuses_url":"https://api.github.com/repos/OpenMW/openmw/statuses/51cd2678aeb094b1b50d417b8d189b0331b4ab56","head":{"label":"MiroslavR:master","ref":"master","sha":"51cd2678aeb094b1b50d417b8d189b0331b4ab56","user":{"login":"MiroslavR","id":7346770,"avatar_url":"https://avatars.githubusercontent.com/u/7346770?v=2","gravatar_id":"f08514e1f1532356434893744caf5dc7","url":"https://api.github.com/users/MiroslavR","html_url":"https://github.com/MiroslavR","followers_url":"https://api.github.com/users/MiroslavR/followers","following_url":"https://api.github.com/users/MiroslavR/following{/other_user}","gists_url":"https://api.github.com/users/MiroslavR/gists{/gist_id}","starred_url":"https://api.github.com/users/MiroslavR/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MiroslavR/subscriptions","organizations_url":"https://api.github.com/users/MiroslavR/orgs","repos_url":"https://api.github.com/users/MiroslavR/repos","events_url":"https://api.github.com/users/MiroslavR/events{/privacy}","received_events_url":"https://api.github.com/users/MiroslavR/received_events","type":"User","site_admin":false},"repo":{"id":22008409,"name":"openmw","full_name":"MiroslavR/openmw","owner":{"login":"MiroslavR","id":7346770,"avatar_url":"https://avatars.githubusercontent.com/u/7346770?v=2","gravatar_id":"f08514e1f1532356434893744caf5dc7","url":"https://api.github.com/users/MiroslavR","html_url":"https://github.com/MiroslavR","followers_url":"https://api.github.com/users/MiroslavR/followers","following_url":"https://api.github.com/users/MiroslavR/following{/other_user}","gists_url":"https://api.github.com/users/MiroslavR/gists{/gist_id}","starred_url":"https://api.github.com/users/MiroslavR/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MiroslavR/subscriptions","organizations_url":"https://api.github.com/users/MiroslavR/orgs","repos_url":"https://api.github.com/users/MiroslavR/repos","events_url":"https://api.github.com/users/MiroslavR/events{/privacy}","received_events_url":"https://api.github.com/users/MiroslavR/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/MiroslavR/openmw","description":"OpenMW is an unofficial open source engine reimplementation of the game Morrowind.","fork":true,"url":"https://api.github.com/repos/MiroslavR/openmw","forks_url":"https://api.github.com/repos/MiroslavR/openmw/forks","keys_url":"https://api.github.com/repos/MiroslavR/openmw/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MiroslavR/openmw/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MiroslavR/openmw/teams","hooks_url":"https://api.github.com/repos/MiroslavR/openmw/hooks","issue_events_url":"https://api.github.com/repos/MiroslavR/openmw/issues/events{/number}","events_url":"https://api.github.com/repos/MiroslavR/openmw/events","assignees_url":"https://api.github.com/repos/MiroslavR/openmw/assignees{/user}","branches_url":"https://api.github.com/repos/MiroslavR/openmw/branches{/branch}","tags_url":"https://api.github.com/repos/MiroslavR/openmw/tags","blobs_url":"https://api.github.com/repos/MiroslavR/openmw/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MiroslavR/openmw/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MiroslavR/openmw/git/refs{/sha}","trees_url":"https://api.github.com/repos/MiroslavR/openmw/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MiroslavR/openmw/statuses/{sha}","languages_url":"https://api.github.com/repos/MiroslavR/openmw/languages","stargazers_url":"https://api.github.com/repos/MiroslavR/openmw/stargazers","contributors_url":"https://api.github.com/repos/MiroslavR/openmw/contributors","subscribers_url":"https://api.github.com/repos/MiroslavR/openmw/subscribers","subscription_url":"https://api.github.com/repos/MiroslavR/openmw/subscription","commits_url":"https://api.github.com/repos/MiroslavR/openmw/commits{/sha}","git_commits_url":"https://api.github.com/repos/MiroslavR/openmw/git/commits{/sha}","comments_url":"https://api.github.com/repos/MiroslavR/openmw/comments{/number}","issue_comment_url":"https://api.github.com/repos/MiroslavR/openmw/issues/comments/{number}","contents_url":"https://api.github.com/repos/MiroslavR/openmw/contents/{+path}","compare_url":"https://api.github.com/repos/MiroslavR/openmw/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MiroslavR/openmw/merges","archive_url":"https://api.github.com/repos/MiroslavR/openmw/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MiroslavR/openmw/downloads","issues_url":"https://api.github.com/repos/MiroslavR/openmw/issues{/number}","pulls_url":"https://api.github.com/repos/MiroslavR/openmw/pulls{/number}","milestones_url":"https://api.github.com/repos/MiroslavR/openmw/milestones{/number}","notifications_url":"https://api.github.com/repos/MiroslavR/openmw/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MiroslavR/openmw/labels{/name}","releases_url":"https://api.github.com/repos/MiroslavR/openmw/releases{/id}","created_at":"2014-07-19T11:36:23Z","updated_at":"2014-07-19T12:05:22Z","pushed_at":"2014-09-21T20:26:03Z","git_url":"git://github.com/MiroslavR/openmw.git","ssh_url":"git@github.com:MiroslavR/openmw.git","clone_url":"https://github.com/MiroslavR/openmw.git","svn_url":"https://github.com/MiroslavR/openmw","homepage":"http://www.openmw.org/","size":36751,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"OpenMW:master","ref":"master","sha":"727a83d09a555adcf672f11fa463859c128cb312","user":{"login":"OpenMW","id":6727417,"avatar_url":"https://avatars.githubusercontent.com/u/6727417?v=2","gravatar_id":"2d1c43892ae3bcfefdf6521c724f37c5","url":"https://api.github.com/users/OpenMW","html_url":"https://github.com/OpenMW","followers_url":"https://api.github.com/users/OpenMW/followers","following_url":"https://api.github.com/users/OpenMW/following{/other_user}","gists_url":"https://api.github.com/users/OpenMW/gists{/gist_id}","starred_url":"https://api.github.com/users/OpenMW/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OpenMW/subscriptions","organizations_url":"https://api.github.com/users/OpenMW/orgs","repos_url":"https://api.github.com/users/OpenMW/repos","events_url":"https://api.github.com/users/OpenMW/events{/privacy}","received_events_url":"https://api.github.com/users/OpenMW/received_events","type":"Organization","site_admin":false},"repo":{"id":285722,"name":"openmw","full_name":"OpenMW/openmw","owner":{"login":"OpenMW","id":6727417,"avatar_url":"https://avatars.githubusercontent.com/u/6727417?v=2","gravatar_id":"2d1c43892ae3bcfefdf6521c724f37c5","url":"https://api.github.com/users/OpenMW","html_url":"https://github.com/OpenMW","followers_url":"https://api.github.com/users/OpenMW/followers","following_url":"https://api.github.com/users/OpenMW/following{/other_user}","gists_url":"https://api.github.com/users/OpenMW/gists{/gist_id}","starred_url":"https://api.github.com/users/OpenMW/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OpenMW/subscriptions","organizations_url":"https://api.github.com/users/OpenMW/orgs","repos_url":"https://api.github.com/users/OpenMW/repos","events_url":"https://api.github.com/users/OpenMW/events{/privacy}","received_events_url":"https://api.github.com/users/OpenMW/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/OpenMW/openmw","description":"OpenMW is an unofficial open source engine reimplementation of the game Morrowind.","fork":false,"url":"https://api.github.com/repos/OpenMW/openmw","forks_url":"https://api.github.com/repos/OpenMW/openmw/forks","keys_url":"https://api.github.com/repos/OpenMW/openmw/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OpenMW/openmw/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OpenMW/openmw/teams","hooks_url":"https://api.github.com/repos/OpenMW/openmw/hooks","issue_events_url":"https://api.github.com/repos/OpenMW/openmw/issues/events{/number}","events_url":"https://api.github.com/repos/OpenMW/openmw/events","assignees_url":"https://api.github.com/repos/OpenMW/openmw/assignees{/user}","branches_url":"https://api.github.com/repos/OpenMW/openmw/branches{/branch}","tags_url":"https://api.github.com/repos/OpenMW/openmw/tags","blobs_url":"https://api.github.com/repos/OpenMW/openmw/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OpenMW/openmw/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OpenMW/openmw/git/refs{/sha}","trees_url":"https://api.github.com/repos/OpenMW/openmw/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OpenMW/openmw/statuses/{sha}","languages_url":"https://api.github.com/repos/OpenMW/openmw/languages","stargazers_url":"https://api.github.com/repos/OpenMW/openmw/stargazers","contributors_url":"https://api.github.com/repos/OpenMW/openmw/contributors","subscribers_url":"https://api.github.com/repos/OpenMW/openmw/subscribers","subscription_url":"https://api.github.com/repos/OpenMW/openmw/subscription","commits_url":"https://api.github.com/repos/OpenMW/openmw/commits{/sha}","git_commits_url":"https://api.github.com/repos/OpenMW/openmw/git/commits{/sha}","comments_url":"https://api.github.com/repos/OpenMW/openmw/comments{/number}","issue_comment_url":"https://api.github.com/repos/OpenMW/openmw/issues/comments/{number}","contents_url":"https://api.github.com/repos/OpenMW/openmw/contents/{+path}","compare_url":"https://api.github.com/repos/OpenMW/openmw/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OpenMW/openmw/merges","archive_url":"https://api.github.com/repos/OpenMW/openmw/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OpenMW/openmw/downloads","issues_url":"https://api.github.com/repos/OpenMW/openmw/issues{/number}","pulls_url":"https://api.github.com/repos/OpenMW/openmw/pulls{/number}","milestones_url":"https://api.github.com/repos/OpenMW/openmw/milestones{/number}","notifications_url":"https://api.github.com/repos/OpenMW/openmw/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OpenMW/openmw/labels{/name}","releases_url":"https://api.github.com/repos/OpenMW/openmw/releases{/id}","created_at":"2009-08-23T14:46:21Z","updated_at":"2014-09-22T07:20:16Z","pushed_at":"2014-09-22T08:12:14Z","git_url":"git://github.com/OpenMW/openmw.git","ssh_url":"git@github.com:OpenMW/openmw.git","clone_url":"https://github.com/OpenMW/openmw.git","svn_url":"https://github.com/OpenMW/openmw","homepage":"http://www.openmw.org/","size":47440,"stargazers_count":333,"watchers_count":333,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":277,"mirror_url":null,"open_issues_count":6,"forks":277,"open_issues":6,"watchers":333,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/282"},"html":{"href":"https://github.com/OpenMW/openmw/pull/282"},"issue":{"href":"https://api.github.com/repos/OpenMW/openmw/issues/282"},"comments":{"href":"https://api.github.com/repos/OpenMW/openmw/issues/282/comments"},"review_comments":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/282/comments"},"review_comment":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/OpenMW/openmw/pulls/282/commits"},"statuses":{"href":"https://api.github.com/repos/OpenMW/openmw/statuses/51cd2678aeb094b1b50d417b8d189b0331b4ab56"}}}}