diff --git a/doomsday/sdk/libappfw/include/de/framework/fontlinewrapping.h b/doomsday/sdk/libappfw/include/de/framework/fontlinewrapping.h index 84030788ce..962eb30539 100644 --- a/doomsday/sdk/libappfw/include/de/framework/fontlinewrapping.h +++ b/doomsday/sdk/libappfw/include/de/framework/fontlinewrapping.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -65,6 +66,9 @@ class LIBAPPFW_PUBLIC FontLineWrapping : public Lockable, public shell::ILineWra void wrapTextToWidth(String const &text, int maxWidth); void wrapTextToWidth(String const &text, Font::RichFormat const &format, int maxWidth); + void rasterizeLines(Rangei const &lineRange); + void clearRasterizedLines() const; + /** * Cancels the ongoing wrapping operation. This is useful when doing long wrapping * operations in the background. An exception is thrown from the ongoing @@ -129,6 +133,16 @@ class LIBAPPFW_PUBLIC FontLineWrapping : public Lockable, public shell::ILineWra */ LineInfo const &lineInfo(int index) const; + /** + * Returns a rasterized version of a segment. + * Before calling this, segments must first be rasterized by calling + * rasterizeAllSegments(). + * + * @param line Line index. + * @param segment Segment on the line. + */ + Image rasterizedSegment(int line, int segment) const; + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/sdk/libappfw/src/fontlinewrapping.cpp b/doomsday/sdk/libappfw/src/fontlinewrapping.cpp index a94452d5f9..fdde77bf35 100644 --- a/doomsday/sdk/libappfw/src/fontlinewrapping.cpp +++ b/doomsday/sdk/libappfw/src/fontlinewrapping.cpp @@ -22,6 +22,7 @@ #include "de/FontLineWrapping" #include "de/BaseGuiApp" +#include #include @@ -64,6 +65,11 @@ DENG2_PIMPL_NOREF(FontLineWrapping) typedef QList Lines; Lines lines; + struct RasterizedLine { + QList segmentImages; + }; + QList rasterized; + int maxWidth; String text; ///< Plain text. Font::RichFormat format; @@ -90,6 +96,7 @@ DENG2_PIMPL_NOREF(FontLineWrapping) { qDeleteAll(lines); lines.clear(); + rasterized.clear(); } String rangeText(Rangei const &range) const @@ -504,6 +511,12 @@ DENG2_PIMPL_NOREF(FontLineWrapping) return lineRange.end + extraLinesProduced; } + + Image rasterizeSegment(LineInfo::Segment const &segment) + { + return font->rasterize(text .substr (segment.range), + format.subRange(segment.range)); + } }; FontLineWrapping::FontLineWrapping() : d(new Impl) @@ -578,7 +591,7 @@ void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat cons // When tabs are used, we must first determine the maximum width of each tab stop. if (d->containsTabs(Rangei(0, text.size()))) { - d->indent = 0; + d->indent = 0; d->tabStop = 0; // Divide the content into lines by newlines. @@ -750,9 +763,52 @@ Vector2i FontLineWrapping::charTopLeftInPixels(int line, int charIndex) FontLineWrapping::LineInfo const &FontLineWrapping::lineInfo(int index) const { + DENG2_ASSERT(line >= 0 && line < d->lines.size()); return d->lines[index]->info; } +void FontLineWrapping::rasterizeLines(Rangei const &lineRange) +{ + d->rasterized.clear(); + + for (int i = 0; i < height(); ++i) + { + Impl::RasterizedLine rasterLine; + if (lineRange.contains(i)) + { + LineInfo const &line = lineInfo(i); + for (int k = 0; k < line.segs.size(); ++k) + { + rasterLine.segmentImages << d->rasterizeSegment(line.segs.at(k)); + } + } + d->rasterized << rasterLine; + } +} + +void FontLineWrapping::clearRasterizedLines() const +{ + d->rasterized.clear(); +} + +Image FontLineWrapping::rasterizedSegment(int line, int segment) const +{ + DENG2_ASSERT(line >= 0); + if (line >= 0 && line < d->rasterized.size()) + { + auto const &rasterLine = d->rasterized.at(line); + if (!rasterLine.segmentImages.isEmpty()) + { + DENG2_ASSERT(segment >= 0 && segment < rasterLine.segmentImages.size()); + return rasterLine.segmentImages.at(segment); + } + } + // Rasterize now, since it wasn't previously rasterized. + return d->rasterizeSegment(lineInfo(line).segs.at(segment)); +} + +//--------------------------------------------------------------------------------------- + int FontLineWrapping::LineInfo::highestTabStop() const { int stop = -1; diff --git a/doomsday/sdk/libappfw/src/gltextcomposer.cpp b/doomsday/sdk/libappfw/src/gltextcomposer.cpp index 458b69aaf4..aae8998907 100644 --- a/doomsday/sdk/libappfw/src/gltextcomposer.cpp +++ b/doomsday/sdk/libappfw/src/gltextcomposer.cpp @@ -195,13 +195,8 @@ DENG2_PIMPL(GLTextComposer) fgColor = format.style().richStyleColor(Font::RichFormat::NormalColor); } - // Set up the background color to be transparent with no - // change of color in the alphablended smooth edges. - Vector4ub bgColor = fgColor; - bgColor.w = 0; - - seg.id = atlas->alloc(font->rasterize(seg.text, format.subRange(seg.range), - fgColor, bgColor)); + Image const segmentImage = wraps->rasterizedSegment(i, k); + seg.id = atlas->alloc(segmentImage.multiplied(fgColor)); } line.segs << seg; } @@ -217,6 +212,8 @@ DENG2_PIMPL(GLTextComposer) changed = true; } + wraps->clearRasterizedLines(); + DENG2_ASSERT(wraps->height() == lines.size()); return changed; diff --git a/doomsday/sdk/libappfw/src/textdrawable.cpp b/doomsday/sdk/libappfw/src/textdrawable.cpp index b071911ef1..ed79dbe156 100644 --- a/doomsday/sdk/libappfw/src/textdrawable.cpp +++ b/doomsday/sdk/libappfw/src/textdrawable.cpp @@ -175,6 +175,11 @@ DENG2_PIMPL(TextDrawable) // This is where most of the time will be spent: _wrapper->wrapTextToWidth(_wrapper->plainText, _wrapper->format, _width); + // Pre-rasterize the first lines of the text. The assumption is that + // longer text will only be visible after scrolling, so it will be + // rasterized as needed. + _wrapper->rasterizeLines(Rangei(0, 10)); // May also take a while... + // Pass the finished wrapping to the owner. { DENG2_GUARD(d);