# SDL\_ttf: Added a separate end-of-text cluster

**URL:** <https://discourse.libsdl.org/t/sdl-ttf-added-a-separate-end-of-text-cluster/54791>\
**Category:** SDL Commits\
**Created:** [October 7, 2024, 7:48am UTC](https://discourse.libsdl.org/t/sdl-ttf-added-a-separate-end-of-text-cluster/54791 "2024-10-07T07:48:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [October 7, 2024, 7:48am UTC](https://discourse.libsdl.org/t/sdl-ttf-added-a-separate-end-of-text-cluster/54791/1 "2024-10-07T07:48:07Z")

</div>

> <https://github.com/libsdl-org/SDL_ttf/commit/aebd2d2545c40e43dbe5aa03c1c2e97ae9677cf7>
>
> This allows moving the cursor past a newline at the end of the text.

```diff
From aebd2d2545c40e43dbe5aa03c1c2e97ae9677cf7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 7 Oct 2024 00:28:15 -0700
Subject: [PATCH] Added a separate end-of-text cluster

This allows moving the cursor past a newline at the end of the text.
---
 examples/editbox.c | 2 +-
 src/SDL_ttf.c | 68 +++++++++++++++++++++++++++-------------------
 2 files changed, 41 insertions(+), 29 deletions(-)

diff --git a/examples/editbox.c b/examples/editbox.c
index d9e807f7..f96d5087 100644
--- a/examples/editbox.c
+++ b/examples/editbox.c
@@ -456,7 +456,7 @@ void EditBox_Draw(EditBox *edit)
 
 static int GetCursorTextIndex(TTF_Font *font, int x, const TTF_SubString *substring)
 {
- if (substring->flags & TTF_SUBSTRING_LINE_END) {
+ if (substring->flags & (TTF_SUBSTRING_LINE_END | TTF_SUBSTRING_TEXT_END)) {
         return substring->offset;
     }
 
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 3fc57a2d..59a45d05 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -3724,10 +3724,11 @@ static int SDLCALL SortClusters(const void *a, const void *b)
     return (A->offset - B->offset);
 }
 
-static int CalculateClusterLengths(TTF_Font *font, TTF_SubString *clusters, int num_clusters, size_t length, int *lines)
+static int CalculateClusterLengths(TTF_Text *text, TTF_SubString *clusters, int num_clusters, size_t length, int *lines)
 {
     SDL_qsort(clusters, num_clusters, sizeof(*clusters), SortClusters);
 
+ TTF_Font *font = text->internal->font;
     int i;
     const TTF_SubString *src = clusters;
     TTF_SubString *last = NULL;
@@ -3769,13 +3770,28 @@ static int CalculateClusterLengths(TTF_Font *font, TTF_SubString *clusters, int
                 cluster->rect.x += cluster->rect.w;
                 cluster->rect.w = 0;
             }
+ } else if (cluster->flags & TTF_SUBSTRING_TEXT_END) {
+ if (last) {
+ if (last->length > 0 && text->text[last->offset + last->length - 1] == '\n') {
+ cluster->line_index = last->line_index + 1;
+ cluster->rect.y = (cluster->line_index * font->lineskip);
+ cluster->rect.h = font->height;
+ } else {
+ cluster->line_index = last->line_index;
+ SDL_copyp(&cluster->rect, &last->rect);
+ cluster->rect.x += cluster->rect.w;
+ cluster->rect.w = 0;
+ }
+ } else {
+ cluster->rect.h = font->height;
+ }
         }
 
         if (i < (num_clusters - 1)) {
             cluster->length = clusters[i + 1].offset - cluster->offset;
         } else {
- cluster->flags |= TTF_SUBSTRING_TEXT_END;
- cluster->length = (int)(length - cluster->offset);
+ SDL_assert(cluster->flags & TTF_SUBSTRING_TEXT_END);
+ SDL_assert(cluster->offset == length);
         }
         last = cluster;
     }
@@ -3810,7 +3826,7 @@ static bool LayoutText(TTF_Text *text)
         goto done;
     }
 
- clusters = (TTF_SubString *)SDL_calloc(font->num_clusters + 1, sizeof(*clusters));
+ clusters = (TTF_SubString *)SDL_calloc(font->num_clusters + 2, sizeof(*clusters));
     if (!clusters) {
         goto done;
     }
@@ -3819,10 +3835,15 @@ static bool LayoutText(TTF_Text *text)
     if (!Render_Line_TextEngine(font, xstart, ystart, width, height, ops, &num_ops, clusters, &num_clusters, 0, 0)) {
         goto done;
     }
+
     cluster = &clusters[num_clusters++];
     cluster->flags = TTF_SUBSTRING_LINE_END;
     cluster->offset = (int)SDL_strlen(text->text);
 
+ cluster = &clusters[num_clusters++];
+ cluster->flags = TTF_SUBSTRING_TEXT_END;
+ cluster->offset = (int)SDL_strlen(text->text);
+
     // Apply underline or strikethrough style, if needed
     if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
         Draw_Line_TextEngine(font, width, height, 0, ystart + font->underline_top_row, width, font->line_thickness, ops, &num_ops);
@@ -3832,7 +3853,7 @@ static bool LayoutText(TTF_Text *text)
         Draw_Line_TextEngine(font, width, height, 0, ystart + font->strikethrough_top_row, width, font->line_thickness, ops, &num_ops);
     }
 
- num_clusters = CalculateClusterLengths(font, clusters, num_clusters, length, NULL);
+ num_clusters = CalculateClusterLengths(text, clusters, num_clusters, length, NULL);
 
     result = true;
 
@@ -3862,7 +3883,7 @@ static bool LayoutTextWrapped(TTF_Text *text)
     TTF_DrawOperation *ops = NULL, *new_ops;
     int num_ops = 0, max_ops = 0, extra_ops = 0, additional_ops;
     TTF_SubString *clusters = NULL, *new_clusters, *cluster;
- int num_clusters = 0, max_clusters = 0, additional_clusters, cluster_offset;
+ int num_clusters = 0, max_clusters = 0, cluster_offset;
     int *lines = NULL;
     bool result = false;
 
@@ -3887,20 +3908,18 @@ static bool LayoutTextWrapped(TTF_Text *text)
         }
     }
 
+ max_clusters = numLines + 1;
+ clusters = (TTF_SubString *)SDL_calloc(max_clusters, sizeof(*clusters));
+ if (!clusters) {
+ goto done;
+ }
+
     // Render each line
     for (i = 0; i < numLines; i++) {
         int xstart, ystart, line_width, xoffset;
 
         if (strLines[i].length == 0) {
- new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + 1) * sizeof(*new_clusters));
- if (!new_clusters) {
- goto done;
- }
- clusters = new_clusters;
- max_clusters += 1;
-
             cluster = &clusters[num_clusters++];
- SDL_zerop(cluster);
             cluster->flags = TTF_SUBSTRING_LINE_END;
             cluster->offset = (int)(uintptr_t)(strLines[i].text - text->text);
             cluster->line_index = i;
@@ -3936,14 +3955,13 @@ static bool LayoutTextWrapped(TTF_Text *text)
         max_ops += additional_ops;
 
         // Allocate space for the clusters on this line
- additional_clusters = (font->num_clusters + 1);
- new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + additional_clusters) * sizeof(*new_clusters));
+ new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + font->num_clusters) * sizeof(*new_clusters));
         if (!new_clusters) {
             goto done;
         }
- SDL_memset(new_clusters + max_clusters, 0, additional_clusters * sizeof(*new_clusters));
+ SDL_memset(new_clusters + max_clusters, 0, font->num_clusters * sizeof(*new_clusters));
         clusters = new_clusters;
- max_clusters += additional_clusters;
+ max_clusters += font->num_clusters;
         cluster_offset = (int)(uintptr_t)(strLines[i].text - text->text);
 
         // Create the text drawing operations
@@ -3964,8 +3982,11 @@ static bool LayoutTextWrapped(TTF_Text *text)
             Draw_Line_TextEngine(font, width, height, xoffset, ystart + font->strikethrough_top_row, line_width, font->line_thickness, ops, &num_ops);
         }
     }
+ cluster = &clusters[num_clusters++];
+ cluster->flags = TTF_SUBSTRING_TEXT_END;
+ cluster->offset = (int)length;
 
- num_clusters = CalculateClusterLengths(font, clusters, num_clusters, length, lines);
+ num_clusters = CalculateClusterLengths(text, clusters, num_clusters, length, lines);
 
     result = true;
 
@@ -4293,9 +4314,6 @@ bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
     int length = (int)SDL_strlen(text->text);
     if (offset >= length) {
         SDL_copyp(substring, &clusters[num_clusters - 1]);
- substring->length = 0;
- substring->rect.x += substring->rect.w;
- substring->rect.w = 0;
         return true;
     }
 
@@ -4366,9 +4384,6 @@ bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substr
 
     if (line >= text->num_lines) {
         SDL_copyp(substring, &clusters[num_clusters - 1]);
- substring->length = 0;
- substring->rect.x += substring->rect.w;
- substring->rect.w = 0;
         return true;
     }
 
@@ -4630,9 +4645,6 @@ bool TTF_GetNextTextSubString(TTF_Text *text, const TTF_SubString *substring, TT
     }
     if (substring->cluster_index == (num_clusters - 1)) {
         SDL_copyp(next, &clusters[num_clusters - 1]);
- next->length = 0;
- next->rect.x += next->rect.w;
- next->rect.w = 0;
     } else {
         SDL_copyp(next, &clusters[substring->cluster_index + 1]);
     }

```
