You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1205 lines
33 KiB
C++
1205 lines
33 KiB
C++
12 years ago
|
#include "bookpage.hpp"
|
||
|
|
||
|
#include "MyGUI_FontManager.h"
|
||
|
#include "MyGUI_RenderItem.h"
|
||
|
#include "MyGUI_RenderManager.h"
|
||
|
#include "MyGUI_TextureUtility.h"
|
||
|
#include "MyGUI_FactoryManager.h"
|
||
|
|
||
|
#include <platform/stdint.h>
|
||
|
|
||
|
#include <components/misc/utf8stream.hpp>
|
||
|
|
||
|
namespace MWGui
|
||
|
{
|
||
|
struct TypesetBook;
|
||
|
struct PageDisplay;
|
||
|
struct BookPage;
|
||
|
}
|
||
|
|
||
|
using namespace MyGUI;
|
||
|
using namespace MWGui;
|
||
|
|
||
|
static bool ucs_space (int code_point);
|
||
|
static bool ucs_line_break (int code_point);
|
||
|
static bool ucs_breaking_space (int code_point);
|
||
|
|
||
|
struct IBookTypesetter::IStyle { virtual ~IStyle () {} };
|
||
|
|
||
|
struct MWGui::TypesetBook : ITypesetBook
|
||
|
{
|
||
|
typedef std::vector <uint8_t> content;
|
||
|
typedef std::list <content> contents;
|
||
|
typedef utf8_stream::point utf8_point;
|
||
|
typedef std::pair <utf8_point, utf8_point> range;
|
||
|
|
||
|
struct style : IBookTypesetter::IStyle
|
||
|
{
|
||
|
IFont* Font;
|
||
|
Colour HotColour;
|
||
|
Colour ActiveColour;
|
||
|
Colour NormalColour;
|
||
|
interactive_id InteractiveId;
|
||
|
|
||
|
bool match (IFont* tstFont, Colour tstHotColour, Colour tstActiveColour, Colour tstNormalColour, intptr_t tstInteractiveId)
|
||
|
{
|
||
|
return (Font == tstFont) &&
|
||
|
partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId);
|
||
|
}
|
||
|
|
||
|
bool match (char const * tstFont, Colour tstHotColour, Colour tstActiveColour, Colour tstNormalColour, intptr_t tstInteractiveId)
|
||
|
{
|
||
|
return (Font->getResourceName () == tstFont) &&
|
||
|
partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId);
|
||
|
}
|
||
|
|
||
|
bool partal_match (Colour tstHotColour, Colour tstActiveColour, Colour tstNormalColour, intptr_t tstInteractiveId)
|
||
|
{
|
||
|
return
|
||
|
(HotColour == tstHotColour ) &&
|
||
|
(ActiveColour == tstActiveColour ) &&
|
||
|
(NormalColour == tstNormalColour ) &&
|
||
|
(InteractiveId == tstInteractiveId ) ;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
typedef std::list <style> styles;
|
||
|
|
||
|
struct run
|
||
|
{
|
||
|
style* Style;
|
||
|
range Range;
|
||
|
int Left, Right;
|
||
|
int PrintableChars;
|
||
|
};
|
||
|
|
||
|
typedef std::vector <run> runs;
|
||
|
|
||
|
struct line
|
||
|
{
|
||
|
runs Runs;
|
||
|
IntRect Rect;
|
||
|
};
|
||
|
|
||
|
typedef std::vector <line> lines;
|
||
|
|
||
|
struct section
|
||
|
{
|
||
|
lines Lines;
|
||
|
IntRect Rect;
|
||
|
};
|
||
|
|
||
|
typedef std::vector <section> sections;
|
||
|
|
||
|
typedef std::pair <int, int> page;
|
||
|
typedef std::vector <page> pages;
|
||
|
|
||
|
pages Pages;
|
||
|
sections Sections;
|
||
|
contents Contents;
|
||
|
styles Styles;
|
||
|
IntRect Rect;
|
||
|
|
||
|
virtual ~TypesetBook () {}
|
||
|
|
||
|
range addContent (IBookTypesetter::utf8_span Text)
|
||
|
{
|
||
|
auto i = Contents.insert (Contents.end (), content (Text.first, Text.second));
|
||
|
|
||
|
if (i->size () == 0)
|
||
|
return range (utf8_point (NULL), utf8_point (NULL));
|
||
|
|
||
|
utf8_point begin = &i->front ();
|
||
|
utf8_point end = &i->front () + i->size ();
|
||
|
|
||
|
return range (begin, end);
|
||
|
}
|
||
|
|
||
|
int pageCount () const { return Pages.size (); }
|
||
|
|
||
|
std::pair <int, int> getSize () const
|
||
|
{
|
||
|
return std::make_pair (Rect.width (), Rect.height ());
|
||
|
}
|
||
|
|
||
|
template <typename visitor>
|
||
|
void visit_runs (int top, int bottom, IFont* Font, visitor const & Visitor) const
|
||
|
{
|
||
|
for (auto i = Sections.begin (); i != Sections.end (); ++i)
|
||
|
{
|
||
|
if (top >= Rect.bottom || bottom <= i->Rect.top)
|
||
|
continue;
|
||
|
|
||
|
for (auto j = i->Lines.begin (); j != i->Lines.end (); ++j)
|
||
|
{
|
||
|
if (top >= j->Rect.bottom || bottom <= j->Rect.top)
|
||
|
continue;
|
||
|
|
||
|
for (auto k = j->Runs.begin (); k != j->Runs.end (); ++k)
|
||
|
if (!Font || k->Style->Font == Font)
|
||
|
Visitor (*i, *j, *k);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename visitor>
|
||
|
void visit_runs (int top, int bottom, visitor const & Visitor) const
|
||
|
{
|
||
|
visit_runs (top, bottom, NULL, Visitor);
|
||
|
}
|
||
|
|
||
|
style * hitTest (int left, int top)
|
||
|
{
|
||
|
for (auto i = Sections.begin (); i != Sections.end (); ++i)
|
||
|
{
|
||
|
if (top < i->Rect.top || top >= i->Rect.bottom)
|
||
|
continue;
|
||
|
|
||
|
auto left1 = left - i->Rect.left;
|
||
|
|
||
|
for (auto j = i->Lines.begin (); j != i->Lines.end (); ++j)
|
||
|
{
|
||
|
if (top < j->Rect.top || top >= j->Rect.bottom)
|
||
|
continue;
|
||
|
|
||
|
auto left2 = left1 - j->Rect.left;
|
||
|
|
||
|
for (auto k = j->Runs.begin (); k != j->Runs.end (); ++k)
|
||
|
{
|
||
|
if (left2 < k->Left || left2 >= k->Right)
|
||
|
continue;
|
||
|
|
||
|
return k->Style;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
IFont* affectedFont (style* Style)
|
||
|
{
|
||
|
for (auto i = Styles.begin (); i != Styles.end (); ++i)
|
||
|
if (&*i == Style)
|
||
|
return i->Font;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct Typesetter;
|
||
|
};
|
||
|
|
||
|
struct TypesetBook::Typesetter : IBookTypesetter
|
||
|
{
|
||
|
typedef TypesetBook book;
|
||
|
typedef std::shared_ptr <book> book_ptr;
|
||
|
|
||
|
int mPageWidth;
|
||
|
int mPageHeight;
|
||
|
|
||
|
book_ptr Book;
|
||
|
section * Section;
|
||
|
line * Line;
|
||
|
run * Run;
|
||
|
|
||
|
std::vector <alignment> mSectionAlignment;
|
||
|
|
||
|
book::content const * mCurrentContent;
|
||
|
alignment mCurrentAlignment;
|
||
|
|
||
|
Typesetter (size_t Width, size_t Height) :
|
||
|
mPageWidth (Width), mPageHeight(Height),
|
||
|
Section (NULL), Line (NULL), Run (NULL),
|
||
|
mCurrentAlignment (alignLeft),
|
||
|
mCurrentContent (NULL)
|
||
|
{
|
||
|
Book = std::make_shared <book> ();
|
||
|
}
|
||
|
|
||
|
virtual ~Typesetter ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
IStyle * createStyle (char const * FontName, Colour FontColour)
|
||
|
{
|
||
|
for (auto i = Book->Styles.begin (); i != Book->Styles.end (); ++i)
|
||
|
if (i->match (FontName, FontColour, FontColour, FontColour, 0))
|
||
|
return &*i;
|
||
|
|
||
|
auto & Style = *Book->Styles.insert (Book->Styles.end (), style ());
|
||
|
|
||
|
Style.Font = FontManager::getInstance().getByName(FontName);
|
||
|
Style.HotColour = FontColour;
|
||
|
Style.ActiveColour = FontColour;
|
||
|
Style.NormalColour = FontColour;
|
||
|
Style.InteractiveId = 0;
|
||
|
|
||
|
return &Style;
|
||
|
}
|
||
|
|
||
|
IStyle* createHotStyle (IStyle * _BaseStyle, coulour NormalColour, coulour HoverColour, coulour ActiveColour, interactive_id Id, bool Unique)
|
||
|
{
|
||
|
auto BaseStyle = dynamic_cast <style*> (_BaseStyle);
|
||
|
|
||
|
if (!Unique)
|
||
|
for (auto i = Book->Styles.begin (); i != Book->Styles.end (); ++i)
|
||
|
if (i->match (BaseStyle->Font, HoverColour, ActiveColour, NormalColour, Id))
|
||
|
return &*i;
|
||
|
|
||
|
auto & Style = *Book->Styles.insert (Book->Styles.end (), style ());
|
||
|
|
||
|
Style.Font = BaseStyle->Font;
|
||
|
Style.HotColour = HoverColour;
|
||
|
Style.ActiveColour = ActiveColour;
|
||
|
Style.NormalColour = NormalColour;
|
||
|
Style.InteractiveId = Id;
|
||
|
|
||
|
return &Style;
|
||
|
}
|
||
|
|
||
|
void write (IStyle * _Style, utf8_span Text)
|
||
|
{
|
||
|
auto text = Book->addContent (Text);
|
||
|
|
||
|
write_impl (dynamic_cast <style*> (_Style), text.first, text.second);
|
||
|
}
|
||
|
|
||
|
intptr_t add_content (utf8_span Text, bool Select)
|
||
|
{
|
||
|
auto i = Book->Contents.insert (Book->Contents.end (), content (Text.first, Text.second));
|
||
|
|
||
|
if (Select)
|
||
|
mCurrentContent = &(*i);
|
||
|
|
||
|
return reinterpret_cast <intptr_t> (&(*i));
|
||
|
}
|
||
|
|
||
|
void select_content (intptr_t contentHandle)
|
||
|
{
|
||
|
mCurrentContent = reinterpret_cast <content const *> (contentHandle);
|
||
|
}
|
||
|
|
||
|
void write (IStyle * Style, size_t Begin, size_t End)
|
||
|
{
|
||
|
assert (mCurrentContent != NULL);
|
||
|
assert (End <= mCurrentContent->size ());
|
||
|
assert (Begin <= mCurrentContent->size ());
|
||
|
|
||
|
utf8_point begin = &mCurrentContent->front () + Begin;
|
||
|
utf8_point end = &mCurrentContent->front () + End ;
|
||
|
|
||
|
write_impl (dynamic_cast <style*> (Style), begin, end);
|
||
|
}
|
||
|
|
||
|
void lineBreak (float margin)
|
||
|
{
|
||
|
assert (margin == 0); //TODO: figure out proper behavior here...
|
||
|
|
||
|
Run = NULL;
|
||
|
Line = NULL;
|
||
|
}
|
||
|
|
||
|
void sectionBreak (float margin)
|
||
|
{
|
||
|
if (Book->Sections.size () > 0)
|
||
|
{
|
||
|
Run = NULL;
|
||
|
Line = NULL;
|
||
|
Section = NULL;
|
||
|
|
||
|
if (Book->Rect.bottom < (Book->Sections.back ().Rect.bottom + margin))
|
||
|
Book->Rect.bottom = (Book->Sections.back ().Rect.bottom + margin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setSectionAlignment (alignment sectionAlignment)
|
||
|
{
|
||
|
if (Section != NULL)
|
||
|
mSectionAlignment.back () = sectionAlignment;
|
||
|
mCurrentAlignment = sectionAlignment;
|
||
|
}
|
||
|
|
||
|
ITypesetBook::ptr complete ()
|
||
|
{
|
||
|
int curPageStart = 0;
|
||
|
int curPageStop = 0;
|
||
|
|
||
|
auto sa = mSectionAlignment.begin ();
|
||
|
for (auto i = Book->Sections.begin (); i != Book->Sections.end (); ++i, ++sa)
|
||
|
{
|
||
|
// apply alignment to individual lines...
|
||
|
for (auto j = i->Lines.begin (); j != i->Lines.end (); ++j)
|
||
|
{
|
||
|
auto width = j->Rect.width ();
|
||
|
auto excess = mPageWidth - width;
|
||
|
|
||
|
switch (*sa)
|
||
|
{
|
||
|
default:
|
||
|
case alignLeft: j->Rect.left = 0; break;
|
||
|
case alignCenter: j->Rect.left = excess/2; break;
|
||
|
case alignRight: j->Rect.left = excess; break;
|
||
|
}
|
||
|
|
||
|
j->Rect.right = j->Rect.left + width;
|
||
|
}
|
||
|
|
||
|
if (curPageStop == curPageStart)
|
||
|
{
|
||
|
curPageStart = i->Rect.top;
|
||
|
curPageStop = i->Rect.top;
|
||
|
}
|
||
|
|
||
|
auto spaceLeft = mPageHeight - (curPageStop - curPageStart);
|
||
|
auto sectionHeight = i->Rect.height ();
|
||
|
|
||
|
if (sectionHeight <= mPageHeight)
|
||
|
{
|
||
|
if (sectionHeight > spaceLeft)
|
||
|
{
|
||
|
assert (curPageStart != curPageStop);
|
||
|
|
||
|
Book->Pages.push_back (page (curPageStart, curPageStop));
|
||
|
|
||
|
curPageStart = i->Rect.top;
|
||
|
curPageStop = i->Rect.bottom;
|
||
|
}
|
||
|
else
|
||
|
curPageStop = i->Rect.bottom;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//split section
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (curPageStart != curPageStop)
|
||
|
Book->Pages.push_back (page (curPageStart, curPageStop));
|
||
|
|
||
|
return Book;
|
||
|
}
|
||
|
|
||
|
void write_impl (style * Style, utf8_stream::point _begin, utf8_stream::point _end)
|
||
|
{
|
||
|
auto line_height = Style->Font->getDefaultHeight ();
|
||
|
|
||
|
utf8_stream stream (_begin, _end);
|
||
|
|
||
|
while (!stream.eof ())
|
||
|
{
|
||
|
if (ucs_line_break (stream.peek ()))
|
||
|
{
|
||
|
stream.consume ();
|
||
|
Line = NULL, Run = NULL;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
int word_width = 0;
|
||
|
int word_height = 0;
|
||
|
int space_width = 0;
|
||
|
int character_count = 0;
|
||
|
|
||
|
auto lead = stream.current ();
|
||
|
|
||
|
while (!stream.eof () && !ucs_line_break (stream.peek ()) && ucs_breaking_space (stream.peek ()))
|
||
|
{
|
||
|
auto gi = Style->Font->getGlyphInfo (stream.peek ());
|
||
|
space_width += gi->advance;
|
||
|
stream.consume ();
|
||
|
}
|
||
|
|
||
|
auto origin = stream.current ();
|
||
|
|
||
|
while (!stream.eof () && !ucs_line_break (stream.peek ()) && !ucs_breaking_space (stream.peek ()))
|
||
|
{
|
||
|
auto gi = Style->Font->getGlyphInfo (stream.peek ());
|
||
|
word_width += gi->advance + gi->bearingX;
|
||
|
word_height = line_height;
|
||
|
++character_count;
|
||
|
stream.consume ();
|
||
|
}
|
||
|
|
||
|
auto extent = stream.current ();
|
||
|
|
||
|
if (lead == extent)
|
||
|
break;
|
||
|
|
||
|
int left = Line ? Line->Rect.right : 0;
|
||
|
|
||
|
if (left + space_width + word_width > mPageWidth)
|
||
|
{
|
||
|
Line = NULL, Run = NULL;
|
||
|
|
||
|
append_run (Style, origin, extent, extent - origin, word_width, Book->Rect.bottom + word_height);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int top = Line ? Line->Rect.top : Book->Rect.bottom;
|
||
|
|
||
|
append_run (Style, lead, extent, extent - origin, left + space_width + word_width, top + word_height);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void append_run (style * Style, utf8_stream::point begin, utf8_stream::point end, int pc, int right, int bottom)
|
||
|
{
|
||
|
if (Section == NULL)
|
||
|
{
|
||
|
Book->Sections.push_back (section ());
|
||
|
Section = &Book->Sections.back ();
|
||
|
Section->Rect = IntRect (0, Book->Rect.bottom, 0, Book->Rect.bottom);
|
||
|
mSectionAlignment.push_back (mCurrentAlignment);
|
||
|
}
|
||
|
|
||
|
if (Line == NULL)
|
||
|
{
|
||
|
Section->Lines.push_back (line ());
|
||
|
Line = &Section->Lines.back ();
|
||
|
Line->Rect = IntRect (0, Section->Rect.bottom, 0, Book->Rect.bottom);
|
||
|
}
|
||
|
|
||
|
if (Book->Rect.right < right)
|
||
|
Book->Rect.right = right;
|
||
|
|
||
|
if (Book->Rect.bottom < bottom)
|
||
|
Book->Rect.bottom = bottom;
|
||
|
|
||
|
if (Section->Rect.right < right)
|
||
|
Section->Rect.right = right;
|
||
|
|
||
|
if (Section->Rect.bottom < bottom)
|
||
|
Section->Rect.bottom = bottom;
|
||
|
|
||
|
if (Line->Rect.right < right)
|
||
|
Line->Rect.right = right;
|
||
|
|
||
|
if (Line->Rect.bottom < bottom)
|
||
|
Line->Rect.bottom = bottom;
|
||
|
|
||
|
if (Run == NULL || Run->Style != Style || Run->Range.second != begin)
|
||
|
{
|
||
|
auto left = Run ? Run->Right : Line->Rect.left;
|
||
|
|
||
|
Line->Runs.push_back (run ());
|
||
|
Run = &Line->Runs.back ();
|
||
|
Run->Style = Style;
|
||
|
Run->Left = left;
|
||
|
Run->Right = right;
|
||
|
Run->Range.first = begin;
|
||
|
Run->Range.second = end;
|
||
|
Run->PrintableChars = pc;
|
||
|
//Run->Locale = Locale;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Run->Right = right;
|
||
|
Run->Range.second = end;
|
||
|
Run->PrintableChars += pc;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
IBookTypesetter::ptr IBookTypesetter::create (int pageWidth, int pageHeight)
|
||
|
{
|
||
|
return std::make_shared <TypesetBook::Typesetter> (pageWidth, pageHeight);
|
||
|
}
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
struct render_xform
|
||
|
{
|
||
|
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;
|
||
|
|
||
|
render_xform (ICroppedRectangle* croppedParent, RenderTargetInfo const & renderTargetInfo)
|
||
|
{
|
||
|
clipTop = croppedParent->_getMarginTop ();
|
||
|
clipLeft = croppedParent->_getMarginLeft ();
|
||
|
clipRight = croppedParent->getWidth () - croppedParent->_getMarginRight ();
|
||
|
clipBottom = croppedParent->getHeight () - croppedParent->_getMarginBottom ();
|
||
|
|
||
|
absoluteLeft = croppedParent->getAbsoluteLeft();
|
||
|
absoluteTop = croppedParent->getAbsoluteTop();
|
||
|
leftOffset = renderTargetInfo.leftOffset;
|
||
|
topOffset = renderTargetInfo.topOffset;
|
||
|
|
||
|
pixScaleX = renderTargetInfo.pixScaleX;
|
||
|
pixScaleY = renderTargetInfo.pixScaleY;
|
||
|
hOffset = renderTargetInfo.hOffset;
|
||
|
vOffset = renderTargetInfo.vOffset;
|
||
|
}
|
||
|
|
||
|
bool clip (FloatRect & vr, 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;
|
||
|
}
|
||
|
|
||
|
FloatPoint operator () (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 glyph_stream
|
||
|
{
|
||
|
float Z;
|
||
|
uint32_t C;
|
||
|
IFont* mFont;
|
||
|
FloatPoint mOrigin;
|
||
|
FloatPoint mCursor;
|
||
|
Vertex* mVertices;
|
||
|
render_xform mRenderXform;
|
||
|
MyGUI::VertexColourType mVertexColourType;
|
||
|
|
||
|
glyph_stream (IFont* font, float left, float top, float Z,
|
||
|
Vertex* vertices, render_xform const & renderXform) :
|
||
|
Z(Z), mOrigin (left, top),
|
||
|
mFont (font), mVertices (vertices),
|
||
|
mRenderXform (renderXform)
|
||
|
{
|
||
|
mVertexColourType = RenderManager::getInstance().getVertexFormat();
|
||
|
}
|
||
|
|
||
|
~glyph_stream ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Vertex* end () const { return mVertices; }
|
||
|
|
||
|
void reset (float left, float top, Colour colour)
|
||
|
{
|
||
|
C = texture_utility::toColourARGB(colour) | 0xFF000000;
|
||
|
texture_utility::convertColour(C, mVertexColourType);
|
||
|
|
||
|
mCursor.left = mOrigin.left + left;
|
||
|
mCursor.top = mOrigin.top + top;
|
||
|
}
|
||
|
|
||
|
void emit_glyph (wchar_t ch)
|
||
|
{
|
||
|
auto gi = mFont->getGlyphInfo (ch);
|
||
|
|
||
|
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;
|
||
|
|
||
|
FloatRect tr = gi->uvRect;
|
||
|
|
||
|
if (mRenderXform.clip (vr, tr))
|
||
|
quad (vr, tr);
|
||
|
|
||
|
mCursor.left += gi->bearingX + gi->advance;
|
||
|
}
|
||
|
|
||
|
void emit_space (wchar_t ch)
|
||
|
{
|
||
|
auto gi = mFont->getGlyphInfo (ch);
|
||
|
|
||
|
mCursor.left += gi->bearingX + gi->advance;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
|
||
|
void quad (const FloatRect& vr, const 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)
|
||
|
{
|
||
|
auto pt = mRenderXform (FloatPoint (X, Y));
|
||
|
|
||
|
mVertices->x = pt.left;
|
||
|
mVertices->y = pt.top ;
|
||
|
mVertices->z = Z;
|
||
|
mVertices->u = U;
|
||
|
mVertices->v = V;
|
||
|
mVertices->colour = C;
|
||
|
|
||
|
++mVertices;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
class MWGui::PageDisplay : public ISubWidgetText
|
||
|
{
|
||
|
MYGUI_RTTI_DERIVED(PageDisplay)
|
||
|
protected:
|
||
|
|
||
|
typedef TypesetBook::section section;
|
||
|
typedef TypesetBook::line line;
|
||
|
typedef TypesetBook::run run;
|
||
|
|
||
|
struct TextFormat : ISubWidget
|
||
|
{
|
||
|
typedef IFont* id;
|
||
|
|
||
|
id mFont;
|
||
|
int mCountVertex;
|
||
|
ITexture* mTexture;
|
||
|
RenderItem* mRenderItem;
|
||
|
PageDisplay * mDisplay;
|
||
|
|
||
|
TextFormat (IFont* Id, PageDisplay * Display) :
|
||
|
mFont (Id),
|
||
|
mTexture (NULL),
|
||
|
mRenderItem (NULL),
|
||
|
mDisplay (Display),
|
||
|
mCountVertex (0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void createDrawItem (ILayerNode* node)
|
||
|
{
|
||
|
assert (mRenderItem == NULL);
|
||
|
|
||
|
if (mTexture != NULL)
|
||
|
{
|
||
|
mRenderItem = node->addToRenderItem(mTexture, false, false);
|
||
|
mRenderItem->addDrawItem(this, mCountVertex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void destroyDrawItem (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(ITexture* _texture, ILayerNode* _node) {}
|
||
|
void destroyDrawItem() {};
|
||
|
};
|
||
|
|
||
|
public:
|
||
|
|
||
|
typedef TypesetBook::style style;
|
||
|
|
||
|
int view_top;
|
||
|
int view_bottom;
|
||
|
|
||
|
style* mFocusItem;
|
||
|
bool mItemActive;
|
||
|
std::function <void (intptr_t)> mLinkClicked;
|
||
|
|
||
|
|
||
|
std::shared_ptr <TypesetBook> mBook;
|
||
|
size_t mPage;
|
||
|
|
||
|
ILayerNode* mNode;
|
||
|
std::map <TextFormat::id, TextFormat*> mActiveTextFormats;
|
||
|
|
||
|
PageDisplay ()
|
||
|
{
|
||
|
mPage = -1;
|
||
|
}
|
||
|
|
||
|
MouseButton mLastDown;
|
||
|
|
||
|
void dirtyFocusItem ()
|
||
|
{
|
||
|
if (mFocusItem != 0)
|
||
|
{
|
||
|
auto Font = mBook->affectedFont (mFocusItem);
|
||
|
|
||
|
auto i = mActiveTextFormats.find (Font);
|
||
|
|
||
|
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 ();
|
||
|
|
||
|
auto Hit = mBook->hitTest (_left, view_top + _top);
|
||
|
|
||
|
if (mLastDown == MouseButton::None)
|
||
|
{
|
||
|
if (Hit != mFocusItem)
|
||
|
{
|
||
|
dirtyFocusItem ();
|
||
|
|
||
|
mFocusItem = Hit;
|
||
|
mItemActive = false;
|
||
|
|
||
|
dirtyFocusItem ();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if (mFocusItem != 0)
|
||
|
{
|
||
|
auto newItemActive = Hit == mFocusItem;
|
||
|
|
||
|
if (newItemActive != mItemActive)
|
||
|
{
|
||
|
mItemActive = newItemActive;
|
||
|
|
||
|
dirtyFocusItem ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void onMouseButtonPressed (int _left, int _top, MouseButton _id)
|
||
|
{
|
||
|
if (!mBook)
|
||
|
return;
|
||
|
|
||
|
_left -= mCroppedParent->getAbsoluteLeft ();
|
||
|
_top -= mCroppedParent->getAbsoluteTop ();
|
||
|
|
||
|
if (mLastDown == MouseButton::None)
|
||
|
{
|
||
|
mFocusItem = mBook->hitTest (_left, view_top + _top);
|
||
|
mItemActive = true;
|
||
|
|
||
|
dirtyFocusItem ();
|
||
|
|
||
|
mLastDown = _id;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void onMouseButtonReleased(int _left, int _top, MouseButton _id)
|
||
|
{
|
||
|
if (!mBook)
|
||
|
return;
|
||
|
|
||
|
_left -= mCroppedParent->getAbsoluteLeft ();
|
||
|
_top -= mCroppedParent->getAbsoluteTop ();
|
||
|
|
||
|
if (mLastDown == _id)
|
||
|
{
|
||
|
auto mItem = mBook->hitTest (_left, view_top + _top);
|
||
|
|
||
|
auto clicked = mFocusItem == mItem;
|
||
|
|
||
|
mItemActive = false;
|
||
|
|
||
|
dirtyFocusItem ();
|
||
|
|
||
|
mLastDown = MouseButton::None;
|
||
|
|
||
|
if (clicked && mLinkClicked && mItem && mItem->InteractiveId != 0)
|
||
|
mLinkClicked (mItem->InteractiveId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void showPage (ITypesetBook::ptr _Book, size_t newPage)
|
||
|
{
|
||
|
auto newBook = std::dynamic_pointer_cast <TypesetBook> (_Book);
|
||
|
|
||
|
if (mBook != newBook)
|
||
|
{
|
||
|
mFocusItem = nullptr;
|
||
|
mItemActive = 0;
|
||
|
|
||
|
for (auto 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;
|
||
|
mPage = newPage;
|
||
|
|
||
|
if (newPage < mBook->Pages.size ())
|
||
|
{
|
||
|
view_top = mBook->Pages [newPage].first;
|
||
|
view_bottom = mBook->Pages [newPage].second;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
view_top = 0;
|
||
|
view_bottom = 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mBook.reset ();
|
||
|
mPage = -1;
|
||
|
view_top = 0;
|
||
|
view_bottom = 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if (mBook && mPage != newPage)
|
||
|
{
|
||
|
if (mNode != NULL)
|
||
|
for (auto i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
|
||
|
mNode->outOfDate(i->second->mRenderItem);
|
||
|
|
||
|
mPage = newPage;
|
||
|
|
||
|
if (newPage < mBook->Pages.size ())
|
||
|
{
|
||
|
view_top = mBook->Pages [newPage].first;
|
||
|
view_bottom = mBook->Pages [newPage].second;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
view_top = 0;
|
||
|
view_bottom = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void createActiveFormats (std::shared_ptr <TypesetBook> newBook)
|
||
|
{
|
||
|
newBook->visit_runs (0, 0x7FFFFFFF, [this] (section const & Section, line const & Line, run const & Run) {
|
||
|
|
||
|
auto Font = Run.Style->Font;
|
||
|
|
||
|
auto j = mActiveTextFormats.find (Font);
|
||
|
|
||
|
if (j == mActiveTextFormats.end ())
|
||
|
{
|
||
|
auto textFormat = new TextFormat (Font, this);
|
||
|
|
||
|
textFormat->mTexture = Font->getTextureFont ();
|
||
|
|
||
|
j = mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first;
|
||
|
}
|
||
|
|
||
|
j->second->mCountVertex += Run.PrintableChars * 6;
|
||
|
|
||
|
});
|
||
|
|
||
|
if (mNode != NULL)
|
||
|
for (auto 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 = MouseButton::None;
|
||
|
mFocusItem = nullptr;
|
||
|
mItemActive = 0;
|
||
|
}
|
||
|
|
||
|
if (nullptr != mNode)
|
||
|
{
|
||
|
for (auto i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
|
||
|
mNode->outOfDate(i->second->mRenderItem);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void createDrawItem(ITexture* _texture, ILayerNode* _node)
|
||
|
{
|
||
|
//test ();
|
||
|
|
||
|
mNode = _node;
|
||
|
|
||
|
for (auto i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
|
||
|
i->second->createDrawItem (_node);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
queue up rendering operations for this text format
|
||
|
*/
|
||
|
void doRender(TextFormat & textFormat)
|
||
|
{
|
||
|
if (!mVisible)
|
||
|
return;
|
||
|
|
||
|
auto vertices = textFormat.mRenderItem->getCurrentVertexBuffer();
|
||
|
|
||
|
render_xform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo());
|
||
|
|
||
|
glyph_stream glyphStream (textFormat.mFont, mCoord.left, mCoord.top-view_top,
|
||
|
-1 /*mNode->getNodeDepth()*/, vertices, renderXform);
|
||
|
|
||
|
auto visit_top = (std::max) (view_top, view_top + int (renderXform.clipTop ));
|
||
|
auto visit_bottom = (std::min) (view_bottom, view_top + int (renderXform.clipBottom));
|
||
|
|
||
|
mBook->visit_runs (visit_top, visit_bottom, textFormat.mFont,
|
||
|
[this, &glyphStream] (section const & Section, line const & Line, run const & Run)
|
||
|
{
|
||
|
bool isActive = Run.Style->InteractiveId && (Run.Style == mFocusItem);
|
||
|
|
||
|
Colour colour = isActive ? (mItemActive ? Run.Style->ActiveColour: Run.Style->HotColour) : Run.Style->NormalColour;
|
||
|
|
||
|
glyphStream.reset (Section.Rect.left + Line.Rect.left + Run.Left, Line.Rect.top, colour);
|
||
|
|
||
|
utf8_stream stream (Run.Range);
|
||
|
|
||
|
while (!stream.eof ())
|
||
|
{
|
||
|
auto code_point = stream.consume ();
|
||
|
|
||
|
if (!ucs_space (code_point))
|
||
|
glyphStream.emit_glyph (code_point);
|
||
|
else
|
||
|
glyphStream.emit_space (code_point);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
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 (auto i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
|
||
|
mNode->outOfDate (i->second->mRenderItem);
|
||
|
|
||
|
}
|
||
|
|
||
|
void destroyDrawItem()
|
||
|
{
|
||
|
for (auto i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
|
||
|
i->second->destroyDrawItem (mNode);
|
||
|
|
||
|
mNode = NULL;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
class MWGui::BookPage : public IBookPage
|
||
|
{
|
||
|
MYGUI_RTTI_DERIVED(BookPage)
|
||
|
public:
|
||
|
|
||
|
|
||
|
void showPage (ITypesetBook::ptr Book, size_t Page)
|
||
|
{
|
||
|
if (auto 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 (std::function <void (interactive_id)> linkClicked)
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->mLinkClicked = linkClicked;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void unadviseLinkClicked ()
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->mLinkClicked = std::function <void (interactive_id)> ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
void onMouseLostFocus(Widget* _new)
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->onMouseLostFocus ();
|
||
|
}
|
||
|
else
|
||
|
Widget::onMouseLostFocus (_new);
|
||
|
}
|
||
|
|
||
|
void onMouseMove(int _left, int _top)
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->onMouseMove (_left, _top);
|
||
|
}
|
||
|
else
|
||
|
Widget::onMouseMove (_left, _top);
|
||
|
}
|
||
|
|
||
|
void onMouseButtonPressed (int _left, int _top, MouseButton _id)
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->onMouseButtonPressed (_left, _top, _id);
|
||
|
}
|
||
|
else
|
||
|
Widget::onMouseButtonPressed (_left, _top, _id);
|
||
|
}
|
||
|
|
||
|
void onMouseButtonReleased(int _left, int _top, MouseButton _id)
|
||
|
{
|
||
|
if (auto pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
|
||
|
{
|
||
|
pd->onMouseButtonReleased (_left, _top, _id);
|
||
|
}
|
||
|
else
|
||
|
Widget::onMouseButtonReleased (_left, _top, _id);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void IBookPage::registerMyGUIComponents ()
|
||
|
{
|
||
|
auto & factory = FactoryManager::getInstance();
|
||
|
|
||
|
factory.registerFactory<BookPage>("Widget");
|
||
|
factory.registerFactory<PageDisplay>("BasisSkin");
|
||
|
}
|
||
|
|
||
|
static bool ucs_line_break (int code_point)
|
||
|
{
|
||
|
return code_point == '\n';
|
||
|
}
|
||
|
|
||
|
static bool ucs_space (int code_point)
|
||
|
{
|
||
|
switch (code_point)
|
||
|
{
|
||
|
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 ucs_breaking_space (int code_point)
|
||
|
{
|
||
|
switch (code_point)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|