From e7273ca21e9fcab6a790c64dc2c1b41a07947e71 Mon Sep 17 00:00:00 2001 From: Sebastien Metrot Date: Wed, 9 Apr 2014 13:23:58 +0200 Subject: [PATCH] much better nuiLabel wrapping added wrapping, justification, left, right and center text layout implementations --- include/nuiTextLayout.h | 1 + include/nuiTextLine.h | 3 + include/nuiTextRun.h | 3 + include/nuiTextStyle.h | 14 ++- src/Font/nuiTextLayout.cpp | 65 +++++++++----- src/Font/nuiTextLine.cpp | 179 +++++++++++++++++++++++++++++++++++++ src/Font/nuiTextRun.cpp | 15 +++- src/Font/nuiTextStyle.cpp | 14 +-- src/Text/nuiLabel.cpp | 2 +- 9 files changed, 263 insertions(+), 33 deletions(-) diff --git a/include/nuiTextLayout.h b/include/nuiTextLayout.h index 4bbacc06..fbb75a17 100644 --- a/include/nuiTextLayout.h +++ b/include/nuiTextLayout.h @@ -109,6 +109,7 @@ class nuiTextLayout : public nuiRefCount float mSpaceWidth = 0; float mTabWidth = 0; + float mWrapX = 0; }; diff --git a/include/nuiTextLine.h b/include/nuiTextLine.h index dfaacf6f..f07579bc 100644 --- a/include/nuiTextLine.h +++ b/include/nuiTextLine.h @@ -34,6 +34,9 @@ class nuiTextLine const nuiTextGlyph* GetGlyph (int32 Offset) const; const nuiTextGlyph* GetGlyphAt (float X, float Y) const; + + float PreLayout(float wrap); + float Layout(float X, float Y, float width, nuiTextLayoutMode mode, nuiRect& globalrect); private: friend class nuiTextLayout; diff --git a/include/nuiTextRun.h b/include/nuiTextRun.h index dde0df6a..d3760b08 100644 --- a/include/nuiTextRun.h +++ b/include/nuiTextRun.h @@ -86,6 +86,8 @@ class nuiTextRun : public nuiRefCount nuiRect GetRect() const; bool IsDummy() const; + bool IsWrapStart() const; + void SetWrapStart(bool set); const nuiTextStyle& GetStyle() const; @@ -101,6 +103,7 @@ class nuiTextRun : public nuiRefCount bool mUnderline : 1; bool mStrikeThrough : 1; bool mDummy : 1; + bool mWrapStart : 1; std::vector mGlyphs; float mX; diff --git a/include/nuiTextStyle.h b/include/nuiTextStyle.h index 47e2cebf..228ac549 100644 --- a/include/nuiTextStyle.h +++ b/include/nuiTextStyle.h @@ -24,6 +24,14 @@ enum nuiTextBaseline nuiTextBaselineSubScript }; +enum nuiTextLayoutMode +{ + nuiTextLayoutJustify, + nuiTextLayoutCenter, + nuiTextLayoutLeft, + nuiTextLayoutRight +}; + class nuiTextStyle { public: @@ -52,8 +60,8 @@ class nuiTextStyle nuiTextBaseline GetBaseline() const; void SetDirection(nuiTextDirection set); nuiTextDirection GetDirection() const; - void SetJustify(bool set); - bool GetJustify() const; + void SetMode(nuiTextLayoutMode set); + nuiTextLayoutMode GetMode() const; private: nuiFontBase* mpFont; @@ -65,7 +73,7 @@ class nuiTextStyle bool mUnderline : 1; bool mStrikeThrough : 1; nuiTextBaseline mBaseline; - bool mJustify : 1; + nuiTextLayoutMode mMode; nuiTextDirection mDirection; }; diff --git a/src/Font/nuiTextLayout.cpp b/src/Font/nuiTextLayout.cpp index 21fd9004..69466616 100644 --- a/src/Font/nuiTextLayout.cpp +++ b/src/Font/nuiTextLayout.cpp @@ -153,7 +153,8 @@ bool nuiTextLayout::Layout(const nglString& rString) nuiRect rect; float PenX = 0; float PenY = 0; - // Assign the correct font to each run + float maxwidth = 0; + // First pass: Assign the correct font to each run, shape the runs and calculate wraping if needed: for (uint32 p = 0; p < mpParagraphs.size(); p++) { Paragraph* pParagraph = mpParagraphs[p]; @@ -182,28 +183,33 @@ bool nuiTextLayout::Layout(const nglString& rString) nuiFontInfo finfo; pFont->GetInfo(finfo); - - - // Prepare glyphs: + std::vector& rGlyphs(pRun->GetGlyphs()); - for (int32 g = 0; g < rGlyphs.size(); g++) + + if (mWrapX > 0) { - nuiTextGlyph& rGlyph(rGlyphs.at(g)); - - pFont->PrepareGlyph(PenX + x, PenY + y, rGlyph); - - const nuiSize W = rGlyph.AdvanceX; - // nuiSize h = finfo.AdvanceMaxH; - const nuiSize X = rGlyph.mX + rGlyph.BearingX; - const nuiSize Y = rGlyph.mY - finfo.Ascender; - const nuiSize H = finfo.Height; - - nuiRect rr(rect); - rect.Union(rr, nuiRect(PenX + x + X, PenY + y + Y, W, H)); + // compute the bounding box: + float runw = 0; + for (int32 g = 0; g < rGlyphs.size(); g++) + { + const nuiTextGlyph& rGlyph(rGlyphs.at(g)); + runw += rGlyph.AdvanceX; + } + + if (PenX + x + runw >= mWrapX) + { + PenX = 0; + x = 0; + y += finfo.Height; + pRun->SetWrapStart(true); + } } + } - + x += pRun->GetAdvanceX(); + maxwidth = MAX(maxwidth, PenX + x); + //y += pRun->GetAdvanceY(); @@ -214,7 +220,24 @@ bool nuiTextLayout::Layout(const nglString& rString) PenY += pLine->GetAdvanceY(); } } - + + PenX = 0; + PenY = 0; + + // Now place the glyphs correctly + for (uint32 p = 0; p < mpParagraphs.size(); p++) + { + Paragraph* pParagraph = mpParagraphs[p]; + for (uint32 l = 0; l < pParagraph->size(); l++) + { + nuiTextLine* pLine = (*pParagraph)[l]; + + PenY = pLine->Layout(PenX, PenY, maxwidth, nuiTextLayoutCenter, rect); + //PenY += pLine->GetAdvanceY(); + } + } + + nuiTextLine* pFirstLine = NULL; if (GetParagraphCount() > 0) if (GetLineCount(0) > 0) @@ -524,12 +547,12 @@ nuiRect nuiTextLayout::GetRect() const void nuiTextLayout::SetWrapX(nuiSize WrapX) { - + mWrapX = WrapX; } nuiSize nuiTextLayout::GetWrapX() const { - return 0; + return mWrapX; } diff --git a/src/Font/nuiTextLine.cpp b/src/Font/nuiTextLine.cpp index 4a70e5c1..d5e08223 100644 --- a/src/Font/nuiTextLine.cpp +++ b/src/Font/nuiTextLine.cpp @@ -139,4 +139,183 @@ const nuiTextGlyph* nuiTextLine::GetGlyphAt(float X, float Y) const return NULL; } +float LayoutDirect(float PenX, float PenY, float globalwidth, float sublinewidth, float spacewidth, std::list& subline, nuiRect& globalrect) +{ + float h = 0; + float x = 0; + float y = 0; + for (nuiTextRun* pRun : subline) + { + nuiFontBase* pFont = pRun->GetFont(); + + nuiFontInfo finfo; + + if (pFont) + pFont->GetInfo(finfo); + + std::vector& rGlyphs(pRun->GetGlyphs()); + + // Prepare glyphs: + for (int32 g = 0; g < rGlyphs.size(); g++) + { + nuiTextGlyph& rGlyph(rGlyphs.at(g)); + + pFont->PrepareGlyph(PenX + x, PenY + y, rGlyph); + + const nuiSize W = rGlyph.AdvanceX; + const nuiSize X = rGlyph.mX + rGlyph.BearingX; + const nuiSize Y = rGlyph.mY - finfo.Ascender; + const nuiSize H = finfo.Height; + + h = MAX(h, H); + nuiRect rr(globalrect); + globalrect.Union(rr, nuiRect(PenX + x + X, PenY + y + Y, W, H)); + } + + x += pRun->GetAdvanceX(); + } + + return h; +} + +float LayoutJustify(float PenX, float PenY, float globalwidth, float sublinewidth, float spacewidth, std::list& subline, nuiRect& globalrect) +{ + float h = 0; + float x = 0; + float y = 0; + + float ratio = (globalwidth - (sublinewidth - spacewidth)) / spacewidth; + for (nuiTextRun* pRun : subline) + { + nuiFontBase* pFont = pRun->GetFont(); + + nuiFontInfo finfo; + pFont->GetInfo(finfo); + + std::vector& rGlyphs(pRun->GetGlyphs()); + + // Prepare glyphs: + for (int32 g = 0; g < rGlyphs.size(); g++) + { + nuiTextGlyph& rGlyph(rGlyphs.at(g)); + + pFont->PrepareGlyph(PenX + x, PenY + y, rGlyph); + + const nuiSize W = rGlyph.AdvanceX; + const nuiSize X = rGlyph.mX + rGlyph.BearingX; + const nuiSize Y = rGlyph.mY - finfo.Ascender; + const nuiSize H = finfo.Height; + + h = MAX(h, H); + nuiRect rr(globalrect); + globalrect.Union(rr, nuiRect(PenX + x + X, PenY + y + Y, W, H)); + } + + if (pRun->IsDummy()) + { + x += pRun->GetAdvanceX() * ratio; + } + else + { + x += pRun->GetAdvanceX(); + } + } + + return h; +} + + +float LayoutLeft(float PenX, float PenY, float globalwidth, float sublinewidth, float spacewidth, std::list& subline, nuiRect& globalrect) +{ + return LayoutDirect(PenX, PenY, globalwidth, sublinewidth, spacewidth, subline, globalrect); +} + + +float LayoutRight(float PenX, float PenY, float globalwidth, float sublinewidth, float spacewidth, std::list& subline, nuiRect& globalrect) +{ + float x = globalwidth - sublinewidth; + return LayoutDirect(PenX + x, PenY, globalwidth, sublinewidth, spacewidth, subline, globalrect); +} + +float LayoutCenter(float PenX, float PenY, float globalwidth, float sublinewidth, float spacewidth, std::list& subline, nuiRect& globalrect) +{ + float x = (globalwidth - sublinewidth) / 2; + return LayoutDirect(PenX + x, PenY, globalwidth, sublinewidth, spacewidth, subline, globalrect); +} + + + +float nuiTextLine::Layout(float PenX, float PenY, float width, nuiTextLayoutMode mode, nuiRect& globalrect) +{ + SetPosition(PenX, PenY); + + PenX = 0; + float x = 0; + float y = 0; + + std::vector< std::list > sublines; + sublines.resize(1); + bool sublinestart = true; + // Prepare sublines: + for (uint32 r = 0; r < GetRunCount(); r++) + { + nuiTextRun* pRun = GetRun(r); + if (pRun->IsWrapStart()) + sublines.resize(sublines.size() + 1); + + sublines.back().push_back(pRun); + } + + // Trim the dummy runs: + for (int l = 1; l < sublines.size(); l++) + { + // Remove dummy text runs from the start of the lines (except the first line): + while (!sublines.empty() && sublines[l].front()->IsDummy()) + sublines[l].pop_front(); + + // Remove dummy text runs from the end of the lines: + while (!sublines.empty() && sublines[l].back()->IsDummy()) + sublines[l].pop_back(); + } + + + // Now position each run inside each subline: + for (int l = 0; l < sublines.size(); l++) + { + float w = 0; + float space = 0; + for (nuiTextRun* pRun : sublines[l]) + { + float a = pRun->GetAdvanceX(); + w += a; + if (pRun->IsDummy()) + space += a; + } + + float h = 0; + switch (mode) + { + case nuiTextLayoutLeft: + h = LayoutLeft(PenX, PenY, width, w, space, sublines[l], globalrect); + break; + + case nuiTextLayoutRight: + h = LayoutRight(PenX, PenY, width, w, space, sublines[l], globalrect); + break; + + case nuiTextLayoutJustify: + h = LayoutJustify(PenX, PenY, width, w, space, sublines[l], globalrect); + break; + + case nuiTextLayoutCenter: + h = LayoutCenter(PenX, PenY, width, w, space, sublines[l], globalrect); + break; + } + + PenY += h; + } + + return PenY; +} + diff --git a/src/Font/nuiTextRun.cpp b/src/Font/nuiTextRun.cpp index c4c37637..0b0385ef 100644 --- a/src/Font/nuiTextRun.cpp +++ b/src/Font/nuiTextRun.cpp @@ -20,6 +20,7 @@ nuiTextRun::nuiTextRun(const nuiTextLayout& rLayout, nuiUnicodeScript script, in mUnderline(false), mStrikeThrough(false), mDummy(false), + mWrapStart(false), mStyle(rStyle) { } @@ -35,7 +36,8 @@ nuiTextRun::nuiTextRun(const nuiTextLayout& rLayout, int32 Position, int32 Lengt mAdvanceY(AdvanceY), mUnderline(false), mStrikeThrough(false), - mDummy(true) + mDummy(true), + mWrapStart(false) { } @@ -186,3 +188,14 @@ const nuiTextStyle& nuiTextRun::GetStyle() const return mStyle; } +bool nuiTextRun::IsWrapStart() const +{ + return mWrapStart; +} + +void nuiTextRun::SetWrapStart(bool set) +{ + mWrapStart = set; +} + + diff --git a/src/Font/nuiTextStyle.cpp b/src/Font/nuiTextStyle.cpp index 115ff32d..c2e290c1 100644 --- a/src/Font/nuiTextStyle.cpp +++ b/src/Font/nuiTextStyle.cpp @@ -17,7 +17,7 @@ nuiTextStyle::nuiTextStyle() mUnderline(false), mStrikeThrough(false), mBaseline(nuiTextBaselineNormal), - mJustify(false), + mMode(nuiTextLayoutLeft), mDirection(nuiLeftToRight) { @@ -33,7 +33,7 @@ nuiTextStyle::nuiTextStyle(const nuiTextStyle& rStyle) mUnderline(rStyle.mUnderline), mStrikeThrough(rStyle.mStrikeThrough), mBaseline(rStyle.mBaseline), - mJustify(rStyle.mJustify), + mMode(rStyle.mMode), mDirection(rStyle.mDirection) { if (mpFont) @@ -66,7 +66,7 @@ nuiTextStyle& nuiTextStyle::operator =(const nuiTextStyle& rStyle) mUnderline = (rStyle.mUnderline); mStrikeThrough = (rStyle.mStrikeThrough); mBaseline = (rStyle.mBaseline); - mJustify = (rStyle.mJustify); + mMode = (rStyle.mMode); mDirection = (rStyle.mDirection); return *this; @@ -175,13 +175,13 @@ nuiTextDirection nuiTextStyle::GetDirection() const return mDirection; } -void nuiTextStyle::SetJustify(bool set) +void nuiTextStyle::SetMode(nuiTextLayoutMode set) { - mJustify = set; + mMode = set; } -bool nuiTextStyle::GetJustify() const +nuiTextLayoutMode nuiTextStyle::GetMode() const { - return mJustify; + return mMode; } diff --git a/src/Text/nuiLabel.cpp b/src/Text/nuiLabel.cpp index 934a76c3..5bd7e308 100644 --- a/src/Text/nuiLabel.cpp +++ b/src/Text/nuiLabel.cpp @@ -301,7 +301,7 @@ void nuiLabel::CalcLayout() { //NGL_OUT(_T("Setting wrapping to %f\n"), mConstraint.mMaxWidth); float wrap1 = mConstraint.mMaxWidth; - float wrap2 = GetMaxIdealWidth(); + float wrap2 = MAX(GetMaxIdealWidth(), GetUserWidth()); float wrap = 0; if (wrap1 > 0 && wrap2 > 0) wrap = MIN(wrap1, wrap2);