Skip to content

Commit

Permalink
Refactor|Client: Lines can be composed of smaller segments
Browse files Browse the repository at this point in the history
Tab stops and alignment of content between lines require that the
parts are positioned at GL drawing time rather than when the text
is wrapped and rasterized.

These changes add the concept of Segments to wrapped lines, where
each line is split into segments based on the tab stops defined with
escape sequences.

Todo: Each segment will be rasterized on a separate image on the atlas
so that they can move freely during GL composition.
  • Loading branch information
skyjake committed Jun 7, 2013
1 parent 5b60f8b commit e6af635
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 26 deletions.
15 changes: 14 additions & 1 deletion doomsday/client/include/ui/widgets/fontlinewrapping.h
Expand Up @@ -78,12 +78,25 @@ class FontLineWrapping : public de::shell::ILineWrapping
*/
de::Vector2i charTopLeftInPixels(int line, int charIndex);

struct LineInfo
{
struct Segment {
de::Rangei range;
int tabStop;
Segment(de::Rangei const &r, int tab = 0) : range(r), tabStop(tab) {}
};
typedef QList<Segment> Segments;
Segments segs;

int indent; ///< Left indentation to apply to the entire line.
};

/**
* Returns the left indentation for a wrapped line.
*
* @param index Wrapped line number.
*/
int lineIndent(int index) const;
LineInfo const &lineInfo(int index) const;

private:
DENG2_PRIVATE(d)
Expand Down
4 changes: 2 additions & 2 deletions doomsday/client/include/ui/widgets/gltextcomposer.h
Expand Up @@ -28,8 +28,8 @@
#include "fontlinewrapping.h"

/**
* Manages lines of text on an atlas and produces geometry for drawing the
* text.
* Allocates and releases lines of text on an atlas and produces geometry for
* drawing the text.
*
* Relies on a pre-existing FontLineWrapping where the text content has been
* wrapped onto multiple lines and laid out appropriately.
Expand Down
107 changes: 85 additions & 22 deletions doomsday/client/src/ui/widgets/fontlinewrapping.cpp
Expand Up @@ -30,21 +30,40 @@ static QChar const NEWLINE('\n');
DENG2_PIMPL_NOREF(FontLineWrapping)
{
Font const *font;
struct Line {

struct Line
{
WrappedLine line;
int width;
int indent;
LineInfo info;
int width; ///< Total width of the line (in pixels).

Line(WrappedLine const &ln = WrappedLine(Rangei()), int w = 0, int ind = 0)
: line(ln), width(w), indent(ind) {}
Line(WrappedLine const &ln = WrappedLine(Rangei()), int lineWidth = 0, int leftIndent = 0)
: line(ln), width(lineWidth)
{
info.indent = leftIndent;
}
};
QList<Line> lines;

typedef QList<Line *> Lines;
Lines lines;

String text; ///< Plain text.
Font::RichFormat format;
int indent; ///< Current left indentation (in pixels).

Instance() : font(0), indent(0) {}

~Instance()
{
clearLines();
}

void clearLines()
{
qDeleteAll(lines);
lines.clear();
}

String rangeText(Rangei const &range) const
{
return text.substr(range.start, range.size());
Expand Down Expand Up @@ -84,14 +103,52 @@ DENG2_PIMPL_NOREF(FontLineWrapping)
return markWidth;
}

void appendLine(Rangei const &range)
void appendLine(Rangei const &range, int width = -1)
{
// Check for possible indent for following lines.
int indentMark = rangeIndentMarkWidth(range);
if(width < 0)
{
// Determine the full width now.
width = rangeVisibleWidth(range);
}

Line *line = new Line(WrappedLine(range), width, indent);

// Determine segments in the line.
int tab = 0;
int pos = range.start;
Font::RichFormat rich = format.subRange(range);
Font::RichFormat::Iterator iter(rich);
while(iter.hasNext())
{
iter.next();

lines << Line(WrappedLine(range), rangeVisibleWidth(range), indent);
if(iter.tabStop() != Font::RichFormat::NoTabStop)
{
int const start = range.start + iter.range().start;
if(start > pos)
{
line->info.segs << LineInfo::Segment(Rangei(pos, start), tab);
pos = start;
}

indent += indentMark;
if(iter.tabStop() == Font::RichFormat::NextTabStop)
{
tab++;
}
else if(iter.tabStop() >= 0)
{
tab = iter.tabStop();
}
}
}

// The final segment.
line->info.segs << LineInfo::Segment(Rangei(pos, range.end), tab);

lines << line;

// Check for possible indent for following lines.
indent += rangeIndentMarkWidth(range);
}

bool isAllSpace(Rangei const &range) const
Expand Down Expand Up @@ -148,6 +205,7 @@ DENG2_PIMPL_NOREF(FontLineWrapping)
{
// Crude search.
int end = findMaxWrapWithStep(8, begin, begin, availableWidth, NULL);

// Accurate search.
return findMaxWrapWithStep(1, begin, end, availableWidth, &wrapPosMax);
}
Expand Down Expand Up @@ -180,7 +238,7 @@ void FontLineWrapping::clear()

void FontLineWrapping::reset()
{
d->lines.clear();
d->clearLines();
d->indent = 0;
}

Expand Down Expand Up @@ -221,7 +279,7 @@ void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat cons
int visWidth = d->rangeAdvanceWidth(range);
if(visWidth <= availWidth)
{
d->lines << Instance::Line(WrappedLine(range), visWidth, d->indent);
d->appendLine(range, visWidth);
break;
}
}
Expand Down Expand Up @@ -268,15 +326,20 @@ void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat cons
if(!d->lines.isEmpty())
{
// Mark the final line.
d->lines.last().line.isFinal = true;
d->lines.last()->line.isFinal = true;
}

/*
qDebug() << "Wrapped:" << d->text;
foreach(Instance::Line const &ln, d->lines)
foreach(Instance::Line const *ln, d->lines)
{
qDebug() << ln.line.range.asText() << d->text.substr(ln.line.range)
<< "indent:" << ln.indent << "tabstart:" << ln.tabStart;
qDebug() << ln->line.range.asText() << d->text.substr(ln->line.range)
<< "indent:" << ln->info.indent << "segments:" << ln->info.segs.size();
foreach(LineInfo::Segment const &s, ln->info.segs)
{
qDebug() << "- seg" << s.range.asText() << d->text.substr(s.range)
<< "tab:" << s.tabStop;
}
}
*/
}
Expand All @@ -289,15 +352,15 @@ String const &FontLineWrapping::text() const
WrappedLine FontLineWrapping::line(int index) const
{
DENG2_ASSERT(index >= 0 && index < height());
return d->lines[index].line;
return d->lines[index]->line;
}

int FontLineWrapping::width() const
{
int w = 0;
for(int i = 0; i < d->lines.size(); ++i)
{
w = de::max(w, d->lines[i].width);
w = de::max(w, d->lines[i]->width);
}
return w;
}
Expand Down Expand Up @@ -357,7 +420,7 @@ Vector2i FontLineWrapping::charTopLeftInPixels(int line, int charIndex)
{
if(line >= height()) return Vector2i();

WrappedLine const span = d->lines[line].line;
WrappedLine const span = d->lines[line]->line;
Rangei const range(span.range.start,
de::min(span.range.end, span.range.start + charIndex));

Expand All @@ -368,7 +431,7 @@ Vector2i FontLineWrapping::charTopLeftInPixels(int line, int charIndex)
return cp;
}

int FontLineWrapping::lineIndent(int index) const
FontLineWrapping::LineInfo const &FontLineWrapping::lineInfo(int index) const
{
return d->lines[index].indent;
return d->lines[index]->info;
}
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/widgets/gltextcomposer.cpp
Expand Up @@ -219,7 +219,7 @@ void GLTextComposer::makeVertices(Vertices &triStrip,
Vector2ui const size = d->atlas->imageRect(d->lines[i].id).size();
Rectanglef const uv = d->atlas->imageRectf(d->lines[i].id);

Vector2f linePos = p + Vector2f(d->wraps->lineIndent(i), 0);
Vector2f linePos = p + Vector2f(d->wraps->lineInfo(i).indent, 0);

// Align the line.
if(lineAlign.testFlag(AlignRight))
Expand Down

0 comments on commit e6af635

Please sign in to comment.