Skip to content

Commit 0802c27

Browse files
authored
Draw text line by line (diasurgical#8379)
1 parent bbe5973 commit 0802c27

14 files changed

Lines changed: 241 additions & 28 deletions

CMake/Tests.cmake

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG AND
170170
kerning_fit_spacing__align_right.png
171171
vertical_overflow.png
172172
vertical_overflow-colors.png
173+
cursor-start.png
174+
cursor-middle.png
175+
cursor-end.png
176+
multiline_cursor-end_first_line.png
177+
multiline_cursor-start_second_line.png
178+
multiline_cursor-middle_second_line.png
179+
multiline_cursor-end_second_line.png
180+
highlight-partial.png
181+
highlight-full.png
182+
multiline_highlight.png
173183
SRC_PREFIX test/fixtures/text_render_integration_test/
174184
OUTPUT_DIR "${DEVILUTIONX_TEST_FIXTURES_OUTPUT_DIRECTORY}/text_render_integration_test"
175185
OUTPUT_VARIABLE _text_render_integration_test_fixtures

Source/engine/render/text_render.cpp

Lines changed: 119 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,88 @@ int GetLineStartX(UiFlags flags, const Rectangle &rect, int lineWidth)
437437
return rect.position.x;
438438
}
439439

440+
void DrawLine(
441+
const Surface &out,
442+
std::string_view text,
443+
Point characterPosition,
444+
Rectangle rect,
445+
UiFlags flags,
446+
int curSpacing,
447+
GameFontTables size,
448+
text_color color,
449+
bool outline,
450+
const TextRenderOptions &opts,
451+
size_t lineStartPos,
452+
int totalWidth)
453+
{
454+
CurrentFont currentFont;
455+
456+
std::string_view lineCopy = text;
457+
458+
size_t currentPos = 0;
459+
460+
size_t cpLen;
461+
462+
const auto maybeDrawCursor = [&]() {
463+
const auto byteIndex = static_cast<int>(lineStartPos + currentPos);
464+
Point position = characterPosition;
465+
if (opts.cursorPosition == byteIndex) {
466+
if (GetAnimationFrame(2, 500) != 0 || opts.cursorStatic) {
467+
FontStack baseFont = LoadFont(size, color, 0);
468+
if (baseFont.has_value()) {
469+
DrawFont(out, position, baseFont.glyph('|'), color, outline);
470+
}
471+
}
472+
if (opts.renderedCursorPositionOut != nullptr) {
473+
*opts.renderedCursorPositionOut = position;
474+
}
475+
}
476+
};
477+
478+
// Start from the beginning of the line
479+
characterPosition.x = GetLineStartX(flags, rect, totalWidth);
480+
481+
while (!lineCopy.empty()) {
482+
char32_t c = DecodeFirstUtf8CodePoint(lineCopy, &cpLen);
483+
if (c == Utf8DecodeError) break;
484+
if (c == ZWSP) {
485+
lineCopy.remove_prefix(cpLen);
486+
continue;
487+
}
488+
489+
if (!currentFont.load(size, color, c)) {
490+
c = U'?';
491+
if (!currentFont.load(size, color, c)) {
492+
app_fatal("Missing fonts");
493+
}
494+
}
495+
const uint8_t frame = c & 0xFF;
496+
497+
const ClxSprite glyph = currentFont.glyph(frame);
498+
const int charWidth = glyph.width();
499+
500+
const auto byteIndex = static_cast<int>(lineStartPos + currentPos);
501+
502+
// Draw highlight
503+
if (byteIndex >= opts.highlightRange.begin && byteIndex < opts.highlightRange.end) {
504+
const bool lastInRange = static_cast<int>(byteIndex + cpLen) == opts.highlightRange.end;
505+
FillRect(out, characterPosition.x, characterPosition.y,
506+
glyph.width() + (lastInRange ? 0 : curSpacing), glyph.height(),
507+
opts.highlightColor);
508+
}
509+
510+
DrawFont(out, characterPosition, glyph, color, outline);
511+
maybeDrawCursor();
512+
513+
// Move to the next position
514+
characterPosition.x += charWidth + curSpacing;
515+
currentPos += cpLen;
516+
lineCopy.remove_prefix(cpLen);
517+
}
518+
assert(currentPos == text.size());
519+
maybeDrawCursor();
520+
}
521+
440522
uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, Point &characterPosition,
441523
int lineWidth, int charactersInLine, int rightMargin, int bottomMargin, GameFontTables size, text_color color, bool outline,
442524
TextRenderOptions &opts)
@@ -455,19 +537,26 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect,
455537
std::string_view remaining = text;
456538
size_t cpLen;
457539

458-
const auto maybeDrawCursor = [&]() {
459-
if (opts.cursorPosition == static_cast<int>(text.size() - remaining.size())) {
460-
Point position = characterPosition;
461-
MaybeWrap(position, 2, rightMargin, position.x, opts.lineHeight);
462-
if (GetAnimationFrame(2, 500) != 0) {
463-
FontStack baseFont = LoadFont(size, color, 0);
464-
if (baseFont.has_value()) {
465-
DrawFont(out, position, baseFont.glyph('|'), color, outline);
466-
}
467-
}
468-
if (opts.renderedCursorPositionOut != nullptr) {
469-
*opts.renderedCursorPositionOut = position;
470-
}
540+
// Track line boundaries
541+
size_t lineStartPos = 0;
542+
size_t lineEndPos = 0;
543+
544+
const auto drawLine = [&]() {
545+
std::string_view lineText = text.substr(lineStartPos, lineEndPos - lineStartPos);
546+
if (!lineText.empty()) {
547+
DrawLine(
548+
out,
549+
lineText,
550+
characterPosition,
551+
rect,
552+
opts.flags,
553+
curSpacing,
554+
size,
555+
color,
556+
outline,
557+
opts,
558+
lineStartPos,
559+
lineWidth);
471560
}
472561
};
473562

@@ -487,8 +576,10 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect,
487576
const uint8_t frame = next & 0xFF;
488577
const uint16_t width = currentFont.glyph(frame).width();
489578
if (next == U'\n' || characterPosition.x + width > rightMargin) {
490-
if (next == '\n')
491-
maybeDrawCursor();
579+
lineEndPos = text.size() - remaining.size();
580+
581+
drawLine();
582+
492583
const int nextLineY = characterPosition.y + opts.lineHeight;
493584
if (nextLineY >= bottomMargin)
494585
break;
@@ -506,26 +597,26 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect,
506597
}
507598
characterPosition.x = GetLineStartX(opts.flags, rect, lineWidth);
508599

600+
// Start a new line
601+
lineStartPos = next == U'\n' ? (text.size() - remaining.size() + cpLen) : (text.size() - remaining.size());
602+
lineEndPos = lineStartPos;
603+
509604
if (next == U'\n')
510605
continue;
511606
}
512607

513-
const ClxSprite glyph = currentFont.glyph(frame);
514-
const auto byteIndex = static_cast<int>(text.size() - remaining.size());
515-
516-
// Draw highlight
517-
if (byteIndex >= opts.highlightRange.begin && byteIndex < opts.highlightRange.end) {
518-
const bool lastInRange = static_cast<int>(byteIndex + cpLen) == opts.highlightRange.end;
519-
FillRect(out, characterPosition.x, characterPosition.y,
520-
glyph.width() + (lastInRange ? 0 : curSpacing), glyph.height(),
521-
opts.highlightColor);
522-
}
608+
// Update end position as we add characters
609+
lineEndPos = text.size() - remaining.size() + cpLen;
523610

524-
DrawFont(out, characterPosition, glyph, color, outline);
525-
maybeDrawCursor();
611+
// Update position for the next character
526612
characterPosition.x += width + curSpacing;
527613
}
528-
maybeDrawCursor();
614+
615+
// Draw any remaining characters in the last line
616+
if (lineStartPos < lineEndPos) {
617+
drawLine();
618+
}
619+
529620
return static_cast<uint32_t>(remaining.data() - text.data());
530621
}
531622

Source/engine/render/text_render.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ struct TextRenderOptions {
155155

156156
/** @brief If a cursor is rendered, the surface coordinates are saved here. */
157157
std::optional<Point> *renderedCursorPositionOut = nullptr;
158+
159+
bool cursorStatic = false;
158160
};
159161

160162
/**
1.27 KB
Loading
1.28 KB
Loading
1.26 KB
Loading
1.26 KB
Loading
1.3 KB
Loading
1.47 KB
Loading
1.48 KB
Loading

0 commit comments

Comments
 (0)