forked from teamnwah/openmw-tes3coop
e197f5318b
conversion from 'const float' to 'int', possible loss of data conversion from 'double' to 'int', possible loss of data conversion from 'float' to 'int', possible loss of data
1352 lines
39 KiB
C++
1352 lines
39 KiB
C++
#include "bookpage.hpp"
|
|
|
|
#include "MyGUI_FontManager.h"
|
|
#include "MyGUI_RenderItem.h"
|
|
#include "MyGUI_RenderManager.h"
|
|
#include "MyGUI_TextureUtility.h"
|
|
#include "MyGUI_FactoryManager.h"
|
|
|
|
#include <stdint.h>
|
|
#include <boost/function.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/make_shared.hpp>
|
|
|
|
#include <components/misc/utf8stream.hpp>
|
|
|
|
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 <uint8_t> Content;
|
|
typedef std::list <Content> Contents;
|
|
typedef Utf8Stream::Point Utf8Point;
|
|
typedef std::pair <Utf8Point, Utf8Point> 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 <StyleImpl> Styles;
|
|
|
|
struct Run
|
|
{
|
|
StyleImpl* mStyle;
|
|
Range mRange;
|
|
int mLeft, mRight;
|
|
int mPrintableChars;
|
|
};
|
|
|
|
typedef std::vector <Run> Runs;
|
|
|
|
struct Line
|
|
{
|
|
Runs mRuns;
|
|
MyGUI::IntRect mRect;
|
|
};
|
|
|
|
typedef std::vector <Line> Lines;
|
|
|
|
struct Section
|
|
{
|
|
Lines mLines;
|
|
MyGUI::IntRect mRect;
|
|
};
|
|
|
|
typedef std::vector <Section> 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 <int, int> Page;
|
|
|
|
typedef std::vector <Page> 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 <unsigned int, unsigned int> getSize () const
|
|
{
|
|
return std::make_pair (mRect.width (), mRect.height ());
|
|
}
|
|
|
|
template <typename Visitor>
|
|
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 <typename Visitor>
|
|
void visitRuns (int top, int bottom, Visitor const & visitor) const
|
|
{
|
|
visitRuns (top, bottom, NULL, visitor);
|
|
}
|
|
|
|
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 <Book> BookPtr;
|
|
typedef std::vector<PartialText>::const_iterator PartialTextConstIterator;
|
|
|
|
int mPageWidth;
|
|
int mPageHeight;
|
|
|
|
BookPtr mBook;
|
|
Section * mSection;
|
|
Line * mLine;
|
|
Run * mRun;
|
|
|
|
std::vector <Alignment> mSectionAlignment;
|
|
std::vector <PartialText> mPartialWhitespace;
|
|
std::vector <PartialText> mPartialWord;
|
|
|
|
Book::Content const * mCurrentContent;
|
|
Alignment mCurrentAlignment;
|
|
|
|
Typesetter (size_t width, size_t height) :
|
|
mPageWidth (width), mPageHeight(height),
|
|
mSection (NULL), mLine (NULL), mRun (NULL),
|
|
mCurrentAlignment (AlignLeft),
|
|
mCurrentContent (NULL)
|
|
{
|
|
mBook = boost::make_shared <Book> ();
|
|
}
|
|
|
|
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 <StyleImpl*> (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 <StyleImpl*> (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 <intptr_t> (&(*i));
|
|
}
|
|
|
|
void selectContent (intptr_t contentHandle)
|
|
{
|
|
add_partial_text();
|
|
|
|
mCurrentContent = reinterpret_cast <Content const *> (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 <StyleImpl*> (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 <Alignment>::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<int>(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<int>(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 <TypesetBookImpl::Typesetter> (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<float>(croppedParent->_getMarginTop());
|
|
clipLeft = static_cast<int>(croppedParent->_getMarginLeft ());
|
|
clipRight = static_cast<int>(croppedParent->getWidth () - croppedParent->_getMarginRight ());
|
|
clipBottom = static_cast<int>(croppedParent->getHeight() - croppedParent->_getMarginBottom());
|
|
|
|
absoluteLeft = static_cast<int>(croppedParent->getAbsoluteLeft());
|
|
absoluteTop = static_cast<int>(croppedParent->getAbsoluteTop());
|
|
leftOffset = static_cast<int>(renderTargetInfo.leftOffset);
|
|
topOffset = static_cast<int>(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), mOrigin (left, top),
|
|
mFont (font), mVertices (vertices),
|
|
mRenderXform (renderXform),
|
|
mC(0)
|
|
{
|
|
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),
|
|
mTexture (NULL),
|
|
mRenderItem (NULL),
|
|
mDisplay (display),
|
|
mCountVertex (0)
|
|
{
|
|
}
|
|
|
|
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 <TextFormat::Id, TextFormat*> ActiveTextFormats;
|
|
|
|
int mViewTop;
|
|
int mViewBottom;
|
|
|
|
Style* mFocusItem;
|
|
bool mItemActive;
|
|
MyGUI::MouseButton mLastDown;
|
|
boost::function <void (intptr_t)> mLinkClicked;
|
|
|
|
|
|
boost::shared_ptr <TypesetBookImpl> 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->hitTest (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;
|
|
|
|
left -= mCroppedParent->getAbsoluteLeft ();
|
|
top -= mCroppedParent->getAbsoluteTop ();
|
|
|
|
if (mLastDown == MyGUI::MouseButton::None)
|
|
{
|
|
mFocusItem = mBook->hitTest (left, mViewTop + top);
|
|
mItemActive = true;
|
|
|
|
dirtyFocusItem ();
|
|
|
|
mLastDown = id;
|
|
}
|
|
}
|
|
|
|
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
|
{
|
|
if (!mBook)
|
|
return;
|
|
|
|
left -= mCroppedParent->getAbsoluteLeft ();
|
|
top -= mCroppedParent->getAbsoluteTop ();
|
|
|
|
if (mLastDown == id)
|
|
{
|
|
Style * mItem = mBook->hitTest (left, mViewTop + top);
|
|
|
|
bool clicked = mFocusItem == mItem;
|
|
|
|
mItemActive = false;
|
|
|
|
dirtyFocusItem ();
|
|
|
|
mLastDown = MyGUI::MouseButton::None;
|
|
|
|
if (clicked && mLinkClicked && mItem && mItem->mInteractiveId != 0)
|
|
mLinkClicked (mItem->mInteractiveId);
|
|
}
|
|
}
|
|
|
|
void showPage (TypesetBook::Ptr book, size_t newPage)
|
|
{
|
|
boost::shared_ptr <TypesetBookImpl> newBook = boost::dynamic_pointer_cast <TypesetBookImpl> (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 <TypesetBookImpl> 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)
|
|
{
|
|
//test ();
|
|
|
|
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<float>(section.mRect.left + line.mRect.left + run.mLeft), static_cast<float>(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<float>(mCoord.left), static_cast<float>(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 ()
|
|
{
|
|
}
|
|
|
|
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:
|
|
|
|
void showPage (TypesetBook::Ptr book, size_t page)
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
pd->showPage (book, page);
|
|
else
|
|
throw std::runtime_error ("The main sub-widget for a BookPage must be a PageDisplay.");
|
|
}
|
|
|
|
void adviseLinkClicked (boost::function <void (InteractiveId)> linkClicked)
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->mLinkClicked = linkClicked;
|
|
}
|
|
}
|
|
|
|
void unadviseLinkClicked ()
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->mLinkClicked = boost::function <void (InteractiveId)> ();
|
|
}
|
|
}
|
|
|
|
protected:
|
|
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.
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->onMouseLostFocus ();
|
|
}
|
|
else
|
|
Widget::onMouseLostFocus (_new);
|
|
}
|
|
|
|
void onMouseMove(int left, int top)
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->onMouseMove (left, top);
|
|
}
|
|
else
|
|
Widget::onMouseMove (left, top);
|
|
}
|
|
|
|
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->onMouseButtonPressed (left, top, id);
|
|
}
|
|
else
|
|
Widget::onMouseButtonPressed (left, top, id);
|
|
}
|
|
|
|
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
|
{
|
|
if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
|
{
|
|
pd->onMouseButtonReleased (left, top, id);
|
|
}
|
|
else
|
|
Widget::onMouseButtonReleased (left, top, id);
|
|
}
|
|
};
|
|
|
|
void BookPage::registerMyGUIComponents ()
|
|
{
|
|
MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance();
|
|
|
|
factory.registerFactory<BookPageImpl>("Widget");
|
|
factory.registerFactory<PageDisplay>("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;
|
|
}
|
|
}
|
|
|
|
}
|