#include "bookpage.hpp" #include "MyGUI_FontManager.h" #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" #include "MyGUI_FactoryManager.h" #include #include #include #include #include namespace MWGui { struct TypesetBookImpl; class PageDisplay; class BookPageImpl; static bool ucsSpace (int codePoint); static bool ucsLineBreak (int codePoint); static bool ucsBreakingSpace (int codePoint); struct BookTypesetter::Style { virtual ~Style () {} }; struct TypesetBookImpl : TypesetBook { typedef std::vector Content; typedef std::list Contents; typedef Utf8Stream::Point Utf8Point; typedef std::pair Range; struct StyleImpl : BookTypesetter::Style { MyGUI::IFont* mFont; MyGUI::Colour mHotColour; MyGUI::Colour mActiveColour; MyGUI::Colour mNormalColour; InteractiveId mInteractiveId; bool match (MyGUI::IFont* tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) { return (mFont == tstFont) && partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); } bool match (char const * tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) { return (mFont->getResourceName () == tstFont) && partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); } bool partal_match (MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) { return (mHotColour == tstHotColour ) && (mActiveColour == tstActiveColour ) && (mNormalColour == tstNormalColour ) && (mInteractiveId == tstInteractiveId ) ; } }; typedef std::list Styles; struct Run { StyleImpl* mStyle; Range mRange; int mLeft, mRight; int mPrintableChars; }; typedef std::vector Runs; struct Line { Runs mRuns; MyGUI::IntRect mRect; }; typedef std::vector Lines; struct Section { Lines mLines; MyGUI::IntRect mRect; }; typedef std::vector
Sections; // Holds "top" and "bottom" vertical coordinates in the source text. // A page is basically a "window" into a portion of the source text, similar to a ScrollView. typedef std::pair Page; typedef std::vector Pages; Pages mPages; Sections mSections; Contents mContents; Styles mStyles; MyGUI::IntRect mRect; virtual ~TypesetBookImpl () {} Range addContent (BookTypesetter::Utf8Span text) { Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); if (i->empty()) return Range (Utf8Point (NULL), Utf8Point (NULL)); Utf8Point begin = &i->front (); Utf8Point end = &i->front () + i->size (); return Range (begin, end); } size_t pageCount () const { return mPages.size (); } std::pair getSize () const { return std::make_pair (mRect.width (), mRect.height ()); } template void visitRuns (int top, int bottom, MyGUI::IFont* Font, Visitor const & visitor) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) { if (top >= mRect.bottom || bottom <= i->mRect.top) continue; for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (top >= j->mRect.bottom || bottom <= j->mRect.top) continue; for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) if (!Font || k->mStyle->mFont == Font) visitor (*i, *j, *k); } } } template void visitRuns (int top, int bottom, Visitor const & visitor) const { visitRuns (top, bottom, NULL, visitor); } /// hit test with a margin for error. only hits on interactive text fragments are reported. StyleImpl * hitTestWithMargin (int left, int top) { StyleImpl * hit = hitTest(left, top); if (hit && hit->mInteractiveId > 0) return hit; const int maxMargin = 10; for (int margin=1; margin < maxMargin; ++margin) { for (int i=0; i<4; ++i) { if (i==0) hit = hitTest(left, top-margin); else if (i==1) hit = hitTest(left, top+margin); else if (i==2) hit = hitTest(left-margin, top); else hit = hitTest(left+margin, top); if (hit && hit->mInteractiveId > 0) return hit; } } return NULL; } StyleImpl * hitTest (int left, int top) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) { if (top < i->mRect.top || top >= i->mRect.bottom) continue; int left1 = left - i->mRect.left; for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (top < j->mRect.top || top >= j->mRect.bottom) continue; int left2 = left1 - j->mRect.left; for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) { if (left2 < k->mLeft || left2 >= k->mRight) continue; return k->mStyle; } } } return nullptr; } MyGUI::IFont* affectedFont (StyleImpl* style) { for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i) if (&*i == style) return i->mFont; return NULL; } struct Typesetter; }; struct TypesetBookImpl::Typesetter : BookTypesetter { struct PartialText { StyleImpl *mStyle; Utf8Stream::Point mBegin; Utf8Stream::Point mEnd; int mWidth; PartialText( StyleImpl *style, Utf8Stream::Point begin, Utf8Stream::Point end, int width) : mStyle(style), mBegin(begin), mEnd(end), mWidth(width) {} }; typedef TypesetBookImpl Book; typedef boost::shared_ptr BookPtr; typedef std::vector::const_iterator PartialTextConstIterator; int mPageWidth; int mPageHeight; BookPtr mBook; Section * mSection; Line * mLine; Run * mRun; std::vector mSectionAlignment; std::vector mPartialWhitespace; std::vector mPartialWord; Book::Content const * mCurrentContent; Alignment mCurrentAlignment; Typesetter (size_t width, size_t height) : mPageWidth (width), mPageHeight(height), mSection (NULL), mLine (NULL), mRun (NULL), mCurrentContent (NULL), mCurrentAlignment (AlignLeft) { mBook = boost::make_shared (); } virtual ~Typesetter () { } Style * createStyle (char const * fontName, Colour fontColour) { if (strcmp(fontName, "") == 0) return createStyle(MyGUI::FontManager::getInstance().getDefaultFont().c_str(), fontColour); for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) if (i->match (fontName, fontColour, fontColour, fontColour, 0)) return &*i; StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); style.mFont = MyGUI::FontManager::getInstance().getByName(fontName); style.mHotColour = fontColour; style.mActiveColour = fontColour; style.mNormalColour = fontColour; style.mInteractiveId = 0; return &style; } Style* createHotStyle (Style* baseStyle, Colour normalColour, Colour hoverColour, Colour activeColour, InteractiveId id, bool unique) { StyleImpl* BaseStyle = static_cast (baseStyle); if (!unique) for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) if (i->match (BaseStyle->mFont, hoverColour, activeColour, normalColour, id)) return &*i; StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); style.mFont = BaseStyle->mFont; style.mHotColour = hoverColour; style.mActiveColour = activeColour; style.mNormalColour = normalColour; style.mInteractiveId = id; return &style; } void write (Style * style, Utf8Span text) { Range range = mBook->addContent (text); writeImpl (static_cast (style), range.first, range.second); } intptr_t addContent (Utf8Span text, bool select) { add_partial_text(); Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second)); if (select) mCurrentContent = &(*i); return reinterpret_cast (&(*i)); } void selectContent (intptr_t contentHandle) { add_partial_text(); mCurrentContent = reinterpret_cast (contentHandle); } void write (Style * style, size_t begin, size_t end) { assert (mCurrentContent != NULL); assert (end <= mCurrentContent->size ()); assert (begin <= mCurrentContent->size ()); Utf8Point begin_ = &mCurrentContent->front () + begin; Utf8Point end_ = &mCurrentContent->front () + end ; writeImpl (static_cast (style), begin_, end_); } void lineBreak (float margin) { assert (margin == 0); //TODO: figure out proper behavior here... add_partial_text(); mRun = NULL; mLine = NULL; } void sectionBreak (int margin) { add_partial_text(); if (mBook->mSections.size () > 0) { mRun = NULL; mLine = NULL; mSection = NULL; if (mBook->mRect.bottom < (mBook->mSections.back ().mRect.bottom + margin)) mBook->mRect.bottom = (mBook->mSections.back ().mRect.bottom + margin); } } void setSectionAlignment (Alignment sectionAlignment) { add_partial_text(); if (mSection != NULL) mSectionAlignment.back () = sectionAlignment; mCurrentAlignment = sectionAlignment; } TypesetBook::Ptr complete () { int curPageStart = 0; int curPageStop = 0; add_partial_text(); std::vector ::iterator sa = mSectionAlignment.begin (); for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa) { // apply alignment to individual lines... for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { int width = j->mRect.width (); int excess = mPageWidth - width; switch (*sa) { default: case AlignLeft: j->mRect.left = 0; break; case AlignCenter: j->mRect.left = excess/2; break; case AlignRight: j->mRect.left = excess; break; } j->mRect.right = j->mRect.left + width; } if (curPageStop == curPageStart) { curPageStart = i->mRect.top; curPageStop = i->mRect.top; } int spaceLeft = mPageHeight - (curPageStop - curPageStart); int sectionHeight = i->mRect.height (); // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. int spaceRequired = (i->mRect.bottom - curPageStop); if (curPageStart == curPageStop) // If this is a new page, the section break is not needed spaceRequired = i->mRect.height(); if (spaceRequired <= mPageHeight) { if (spaceRequired > spaceLeft) { // The section won't completely fit on the current page. Finish the current page and start a new one. assert (curPageStart != curPageStop); mBook->mPages.push_back (Page (curPageStart, curPageStop)); curPageStart = i->mRect.top; curPageStop = i->mRect.bottom; } else curPageStop = i->mRect.bottom; } else { //split section int sectionHeightLeft = sectionHeight; while (sectionHeightLeft > mPageHeight) { // Adjust to the top of the first line that does not fit on the current page anymore int splitPos = curPageStop; for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (j->mRect.bottom > curPageStart + mPageHeight) { splitPos = j->mRect.top; break; } } mBook->mPages.push_back (Page (curPageStart, splitPos)); curPageStart = splitPos; curPageStop = splitPos; sectionHeightLeft = (i->mRect.bottom - splitPos); } curPageStop = i->mRect.bottom; } } if (curPageStart != curPageStop) mBook->mPages.push_back (Page (curPageStart, curPageStop)); return mBook; } void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end) { Utf8Stream stream (_begin, _end); while (!stream.eof ()) { if (ucsLineBreak (stream.peek ())) { add_partial_text(); stream.consume (); mLine = NULL, mRun = NULL; continue; } if (ucsBreakingSpace (stream.peek ()) && !mPartialWord.empty()) add_partial_text(); int word_width = 0; int space_width = 0; Utf8Stream::Point lead = stream.current (); while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) { MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); if (gi) space_width += static_cast(gi->advance + gi->bearingX); stream.consume (); } Utf8Stream::Point origin = stream.current (); while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) { MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); if (gi) word_width += static_cast(gi->advance + gi->bearingX); stream.consume (); } Utf8Stream::Point extent = stream.current (); if (lead == extent) break; if ( lead != origin ) mPartialWhitespace.push_back (PartialText (style, lead, origin, space_width)); if ( origin != extent ) mPartialWord.push_back (PartialText (style, origin, extent, word_width)); } } void add_partial_text () { if (mPartialWhitespace.empty() && mPartialWord.empty()) return; int space_width = 0; int word_width = 0; for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) space_width += i->mWidth; for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) word_width += i->mWidth; int left = mLine ? mLine->mRect.right : 0; if (left + space_width + word_width > mPageWidth) { mLine = NULL, mRun = NULL, left = 0; } else { for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; int line_height = i->mStyle->mFont->getDefaultHeight (); append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + line_height); left = mLine->mRect.right; } } for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; int line_height = i->mStyle->mFont->getDefaultHeight (); append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + line_height); left = mLine->mRect.right; } mPartialWhitespace.clear(); mPartialWord.clear(); } void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) { if (mSection == NULL) { mBook->mSections.push_back (Section ()); mSection = &mBook->mSections.back (); mSection->mRect = MyGUI::IntRect (0, mBook->mRect.bottom, 0, mBook->mRect.bottom); mSectionAlignment.push_back (mCurrentAlignment); } if (mLine == NULL) { mSection->mLines.push_back (Line ()); mLine = &mSection->mLines.back (); mLine->mRect = MyGUI::IntRect (0, mSection->mRect.bottom, 0, mBook->mRect.bottom); } if (mBook->mRect.right < right) mBook->mRect.right = right; if (mBook->mRect.bottom < bottom) mBook->mRect.bottom = bottom; if (mSection->mRect.right < right) mSection->mRect.right = right; if (mSection->mRect.bottom < bottom) mSection->mRect.bottom = bottom; if (mLine->mRect.right < right) mLine->mRect.right = right; if (mLine->mRect.bottom < bottom) mLine->mRect.bottom = bottom; if (mRun == NULL || mRun->mStyle != style || mRun->mRange.second != begin) { int left = mRun ? mRun->mRight : mLine->mRect.left; mLine->mRuns.push_back (Run ()); mRun = &mLine->mRuns.back (); mRun->mStyle = style; mRun->mLeft = left; mRun->mRight = right; mRun->mRange.first = begin; mRun->mRange.second = end; mRun->mPrintableChars = pc; //Run->Locale = Locale; } else { mRun->mRight = right; mRun->mRange.second = end; mRun->mPrintableChars += pc; } } }; BookTypesetter::Ptr BookTypesetter::create (int pageWidth, int pageHeight) { return boost::make_shared (pageWidth, pageHeight); } namespace { struct RenderXform { public: float clipTop; float clipLeft; float clipRight; float clipBottom; float absoluteLeft; float absoluteTop; float leftOffset; float topOffset; float pixScaleX; float pixScaleY; float hOffset; float vOffset; RenderXform (MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const & renderTargetInfo) { clipTop = static_cast(croppedParent->_getMarginTop()); clipLeft = static_cast(croppedParent->_getMarginLeft ()); clipRight = static_cast(croppedParent->getWidth () - croppedParent->_getMarginRight ()); clipBottom = static_cast(croppedParent->getHeight() - croppedParent->_getMarginBottom()); absoluteLeft = static_cast(croppedParent->getAbsoluteLeft()); absoluteTop = static_cast(croppedParent->getAbsoluteTop()); leftOffset = static_cast(renderTargetInfo.leftOffset); topOffset = static_cast(renderTargetInfo.topOffset); pixScaleX = renderTargetInfo.pixScaleX; pixScaleY = renderTargetInfo.pixScaleY; hOffset = renderTargetInfo.hOffset; vOffset = renderTargetInfo.vOffset; } bool clip (MyGUI::FloatRect & vr, MyGUI::FloatRect & tr) { if (vr.bottom <= clipTop || vr.right <= clipLeft || vr.left >= clipRight || vr.top >= clipBottom ) return false; if (vr.top < clipTop) { tr.top += tr.height () * (clipTop - vr.top) / vr.height (); vr.top = clipTop; } if (vr.left < clipLeft) { tr.left += tr.width () * (clipLeft - vr.left) / vr.width (); vr.left = clipLeft; } if (vr.right > clipRight) { tr.right -= tr.width () * (vr.right - clipRight) / vr.width (); vr.right = clipRight; } if (vr.bottom > clipBottom) { tr.bottom -= tr.height () * (vr.bottom - clipBottom) / vr.height (); vr.bottom = clipBottom; } return true; } MyGUI::FloatPoint operator () (MyGUI::FloatPoint pt) { pt.left = absoluteLeft - leftOffset + pt.left; pt.top = absoluteTop - topOffset + pt.top; pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f); pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f); return pt; } }; struct GlyphStream { float mZ; uint32_t mC; MyGUI::IFont* mFont; MyGUI::FloatPoint mOrigin; MyGUI::FloatPoint mCursor; MyGUI::Vertex* mVertices; RenderXform mRenderXform; MyGUI::VertexColourType mVertexColourType; GlyphStream (MyGUI::IFont* font, float left, float top, float Z, MyGUI::Vertex* vertices, RenderXform const & renderXform) : mZ(Z), mC(0), mFont (font), mOrigin (left, top), mVertices (vertices), mRenderXform (renderXform) { mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); } ~GlyphStream () { } MyGUI::Vertex* end () const { return mVertices; } void reset (float left, float top, MyGUI::Colour colour) { mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000; MyGUI::texture_utility::convertColour(mC, mVertexColourType); mCursor.left = mOrigin.left + left; mCursor.top = mOrigin.top + top; } void emitGlyph (wchar_t ch) { MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); if (!gi) return; MyGUI::FloatRect vr; vr.left = mCursor.left + gi->bearingX; vr.top = mCursor.top + gi->bearingY; vr.right = vr.left + gi->width; vr.bottom = vr.top + gi->height; MyGUI::FloatRect tr = gi->uvRect; if (mRenderXform.clip (vr, tr)) quad (vr, tr); mCursor.left += gi->bearingX + gi->advance; } void emitSpace (wchar_t ch) { MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); if (gi) mCursor.left += gi->bearingX + gi->advance; } private: void quad (const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr) { vertex (vr.left, vr.top, tr.left, tr.top); vertex (vr.right, vr.top, tr.right, tr.top); vertex (vr.left, vr.bottom, tr.left, tr.bottom); vertex (vr.right, vr.top, tr.right, tr.top); vertex (vr.left, vr.bottom, tr.left, tr.bottom); vertex (vr.right, vr.bottom, tr.right, tr.bottom); } void vertex (float x, float y, float u, float v) { MyGUI::FloatPoint pt = mRenderXform (MyGUI::FloatPoint (x, y)); mVertices->x = pt.left; mVertices->y = pt.top ; mVertices->z = mZ; mVertices->u = u; mVertices->v = v; mVertices->colour = mC; ++mVertices; } }; } class PageDisplay : public MyGUI::ISubWidgetText { MYGUI_RTTI_DERIVED(PageDisplay) protected: typedef TypesetBookImpl::Section Section; typedef TypesetBookImpl::Line Line; typedef TypesetBookImpl::Run Run; bool mIsPageReset; size_t mPage; struct TextFormat : ISubWidget { typedef MyGUI::IFont* Id; Id mFont; int mCountVertex; MyGUI::ITexture* mTexture; MyGUI::RenderItem* mRenderItem; PageDisplay * mDisplay; TextFormat (MyGUI::IFont* id, PageDisplay * display) : mFont (id), mCountVertex (0), mTexture (NULL), mRenderItem (NULL), mDisplay (display) { } void createDrawItem (MyGUI::ILayerNode* node) { assert (mRenderItem == NULL); if (mTexture != NULL) { mRenderItem = node->addToRenderItem(mTexture, false, false); mRenderItem->addDrawItem(this, mCountVertex); } } void destroyDrawItem (MyGUI::ILayerNode* node) { assert (mTexture != NULL ? mRenderItem != NULL : mRenderItem == NULL); if (mTexture != NULL) { mRenderItem->removeDrawItem (this); mRenderItem = NULL; } } void doRender() { mDisplay->doRender (*this); } // this isn't really a sub-widget, its just a "drawitem" which // should have its own interface void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) {} void destroyDrawItem() {}; }; void resetPage() { mIsPageReset = true; mPage = 0; } void setPage(size_t page) { mIsPageReset = false; mPage = page; } bool isPageDifferent(size_t page) { return mIsPageReset || (mPage != page); } public: typedef TypesetBookImpl::StyleImpl Style; typedef std::map ActiveTextFormats; int mViewTop; int mViewBottom; Style* mFocusItem; bool mItemActive; MyGUI::MouseButton mLastDown; boost::function mLinkClicked; boost::shared_ptr mBook; MyGUI::ILayerNode* mNode; ActiveTextFormats mActiveTextFormats; PageDisplay () { resetPage (); mViewTop = 0; mViewBottom = 0; mFocusItem = NULL; mItemActive = false; mNode = NULL; } void dirtyFocusItem () { if (mFocusItem != 0) { MyGUI::IFont* Font = mBook->affectedFont (mFocusItem); ActiveTextFormats::iterator i = mActiveTextFormats.find (Font); if (mNode) mNode->outOfDate (i->second->mRenderItem); } } void onMouseLostFocus () { if (!mBook) return; dirtyFocusItem (); mFocusItem = 0; mItemActive = false; } void onMouseMove (int left, int top) { if (!mBook) return; left -= mCroppedParent->getAbsoluteLeft (); top -= mCroppedParent->getAbsoluteTop (); Style * hit = mBook->hitTestWithMargin (left, mViewTop + top); if (mLastDown == MyGUI::MouseButton::None) { if (hit != mFocusItem) { dirtyFocusItem (); mFocusItem = hit; mItemActive = false; dirtyFocusItem (); } } else if (mFocusItem != 0) { bool newItemActive = hit == mFocusItem; if (newItemActive != mItemActive) { mItemActive = newItemActive; dirtyFocusItem (); } } } void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { if (!mBook) return; // work around inconsistency in MyGUI where the mouse press coordinates aren't // transformed by the current Layer (even though mouse *move* events are). MyGUI::IntPoint pos (left, top); #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) pos = mNode->getLayer()->getPosition(left, top); #endif pos.left -= mCroppedParent->getAbsoluteLeft (); pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == MyGUI::MouseButton::None) { mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); mItemActive = true; dirtyFocusItem (); mLastDown = id; } } void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { if (!mBook) return; // work around inconsistency in MyGUI where the mouse release coordinates aren't // transformed by the current Layer (even though mouse *move* events are). MyGUI::IntPoint pos (left, top); #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) pos = mNode->getLayer()->getPosition(left, top); #endif pos.left -= mCroppedParent->getAbsoluteLeft (); pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == id) { Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); bool clicked = mFocusItem == item; mItemActive = false; dirtyFocusItem (); mLastDown = MyGUI::MouseButton::None; if (clicked && mLinkClicked && item && item->mInteractiveId != 0) mLinkClicked (item->mInteractiveId); } } void showPage (TypesetBook::Ptr book, size_t newPage) { boost::shared_ptr newBook = boost::dynamic_pointer_cast (book); if (mBook != newBook) { mFocusItem = nullptr; mItemActive = 0; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) { if (mNode != NULL) i->second->destroyDrawItem (mNode); delete i->second; } mActiveTextFormats.clear (); if (newBook != NULL) { createActiveFormats (newBook); mBook = newBook; setPage (newPage); if (newPage < mBook->mPages.size ()) { mViewTop = mBook->mPages [newPage].first; mViewBottom = mBook->mPages [newPage].second; } else { mViewTop = 0; mViewBottom = 0; } } else { mBook.reset (); resetPage (); mViewTop = 0; mViewBottom = 0; } } else if (mBook && isPageDifferent (newPage)) { if (mNode != NULL) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); setPage (newPage); if (newPage < mBook->mPages.size ()) { mViewTop = mBook->mPages [newPage].first; mViewBottom = mBook->mPages [newPage].second; } else { mViewTop = 0; mViewBottom = 0; } } } struct CreateActiveFormat { PageDisplay * this_; CreateActiveFormat (PageDisplay * this_) : this_ (this_) {} void operator () (Section const & section, Line const & line, Run const & run) const { MyGUI::IFont* Font = run.mStyle->mFont; ActiveTextFormats::iterator j = this_->mActiveTextFormats.find (Font); if (j == this_->mActiveTextFormats.end ()) { TextFormat * textFormat = new TextFormat (Font, this_); textFormat->mTexture = Font->getTextureFont (); j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first; } j->second->mCountVertex += run.mPrintableChars * 6; } }; void createActiveFormats (boost::shared_ptr newBook) { newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this)); if (mNode != NULL) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->createDrawItem (mNode); } void setVisible (bool newVisible) { if (mVisible == newVisible) return; mVisible = newVisible; if (mVisible) { // reset input state mLastDown = MyGUI::MouseButton::None; mFocusItem = nullptr; mItemActive = 0; } if (nullptr != mNode) { for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); } } void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) { mNode = node; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->createDrawItem (node); } struct RenderRun { PageDisplay * this_; GlyphStream &glyphStream; RenderRun (PageDisplay * this_, GlyphStream &glyphStream) : this_(this_), glyphStream (glyphStream) { } void operator () (Section const & section, Line const & line, Run const & run) const { bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem); MyGUI::Colour colour = isActive ? (this_->mItemActive ? run.mStyle->mActiveColour: run.mStyle->mHotColour) : run.mStyle->mNormalColour; glyphStream.reset(static_cast(section.mRect.left + line.mRect.left + run.mLeft), static_cast(line.mRect.top), colour); Utf8Stream stream (run.mRange); while (!stream.eof ()) { Utf8Stream::UnicodeChar code_point = stream.consume (); if (!ucsSpace (code_point)) glyphStream.emitGlyph (code_point); else glyphStream.emitSpace (code_point); } } }; /* queue up rendering operations for this text format */ void doRender(TextFormat & textFormat) { if (!mVisible) return; MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer(); RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), -1 /*mNode->getNodeDepth()*/, vertices, renderXform); int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop )); int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom)); mBook->visitRuns (visit_top, visit_bottom, textFormat.mFont, RenderRun (this, glyphStream)); textFormat.mRenderItem->setLastVertexCount(glyphStream.end () - vertices); } // ISubWidget should not necessarily be a drawitem // in this case, it is not... void doRender() { } void _updateView () { _checkMargin(); if (mNode != NULL) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); } void _correctView() { _checkMargin (); if (mNode != NULL) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); } void destroyDrawItem() { for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->destroyDrawItem (mNode); mNode = NULL; } }; class BookPageImpl : public BookPage { MYGUI_RTTI_DERIVED(BookPage) public: BookPageImpl() : mPageDisplay(NULL) { } void showPage (TypesetBook::Ptr book, size_t page) { mPageDisplay->showPage (book, page); } void adviseLinkClicked (boost::function linkClicked) { mPageDisplay->mLinkClicked = linkClicked; } void unadviseLinkClicked () { mPageDisplay->mLinkClicked = boost::function (); } protected: virtual void initialiseOverride() { Base::initialiseOverride(); if (getSubWidgetText()) { mPageDisplay = getSubWidgetText()->castType(); } else { throw std::runtime_error("BookPage unable to find page display sub widget"); } } void onMouseLostFocus(Widget* _new) { // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). // Child widgets may already be destroyed! So be careful. mPageDisplay->onMouseLostFocus (); } void onMouseMove(int left, int top) { mPageDisplay->onMouseMove (left, top); } void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { mPageDisplay->onMouseButtonPressed (left, top, id); } void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { mPageDisplay->onMouseButtonReleased (left, top, id); } PageDisplay* mPageDisplay; }; void BookPage::registerMyGUIComponents () { MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance(); factory.registerFactory("Widget"); factory.registerFactory("BasisSkin"); } static bool ucsLineBreak (int codePoint) { return codePoint == '\n'; } static bool ucsSpace (int codePoint) { switch (codePoint) { case 0x0020: // SPACE case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD case 0x2001: // EM QUAD case 0x2002: // EN SPACE case 0x2003: // EM SPACE case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x200B: // ZERO WIDTH SPACE case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x3000: // IDEOGRAPHIC SPACE case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE return true; default: return false; } } static bool ucsBreakingSpace (int codePoint) { switch (codePoint) { case 0x0020: // SPACE //case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD case 0x2001: // EM QUAD case 0x2002: // EN SPACE case 0x2003: // EM SPACE case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x200B: // ZERO WIDTH SPACE case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x3000: // IDEOGRAPHIC SPACE //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE return true; default: return false; } } }