From 5b44a2709b60f4135c2d223c7955ee62af804079 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 3 Oct 2024 11:06:39 -0700 Subject: [PATCH 1/2] Added a quick speedup to TTF_GetTextSubString() for ASCII text --- src/SDL_ttf.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c index 12ff7d6e..549a2bb9 100644 --- a/src/SDL_ttf.c +++ b/src/SDL_ttf.c @@ -4206,11 +4206,17 @@ bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *sub return true; } - // Do a binary search to find the cluster + // Make a quick guess that works for ASCII text with no line breaks int num_clusters = text->internal->num_clusters; const TTF_SubString *clusters = text->internal->clusters; - const TTF_SubString *closest = NULL; const TTF_SubString *cluster = NULL; + if (offset < num_clusters && clusters[offset].offset == offset) { + SDL_copyp(substring, &clusters[offset]); + return true; + } + + // Do a binary search to find the cluster + const TTF_SubString *closest = NULL; int low = 0; int high = num_clusters - 1; while (low <= high) { From dcc9b5e4cd51e150da627c1eab84f4ae9d2de081 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 3 Oct 2024 15:26:05 -0700 Subject: [PATCH 2/2] Added text line wrapping and substring range information --- examples/showfont.c | 150 ++++++++++++++++++++--- include/SDL3_ttf/SDL_ttf.h | 52 ++++++-- src/SDL_ttf.c | 239 ++++++++++++++++++++++++++++++++++--- src/SDL_ttf.sym | 4 +- 4 files changed, 405 insertions(+), 40 deletions(-) diff --git a/examples/showfont.c b/examples/showfont.c index 6c6339b8..2e0b3c67 100644 --- a/examples/showfont.c +++ b/examples/showfont.c @@ -72,8 +72,22 @@ typedef struct { int cursor; bool cursorVisible; Uint64 lastCursorChange; + bool highlighting; + int highlight1, highlight2; } Scene; +static bool GetHighlightExtents(Scene *scene, int *marker1, int *marker2) +{ + if (scene->highlight1 >= 0 && scene->highlight2 >= 0) { + *marker1 = SDL_min(scene->highlight1, scene->highlight2); + *marker2 = SDL_max(scene->highlight1, scene->highlight2) - 1; + if (*marker2 > *marker1) { + return true; + } + } + return false; +} + static void DrawScene(Scene *scene) { SDL_Renderer *renderer = scene->renderer; @@ -96,10 +110,26 @@ static void DrawScene(Scene *scene) focusRect.y -= 1; focusRect.w += 2; focusRect.h += 2; - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF); + SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); SDL_RenderRect(renderer, &focusRect); } + int marker1, marker2; + if (GetHighlightExtents(scene, &marker1, &marker2)) { + TTF_SubString **highlights = TTF_GetTextSubStringsForRange(scene->text, marker1, marker2, NULL); + if (highlights) { + SDL_SetRenderDrawColor(renderer, 0xCC, 0xCC, 0x00, 0xFF); + for (int i = 0; highlights[i]; ++i) { + SDL_FRect rect; + SDL_RectToFRect(&highlights[i]->rect, &rect); + rect.x += x; + rect.y += y; + SDL_RenderFillRect(renderer, &rect); + } + SDL_free(highlights); + } + } + switch (scene->textEngine) { case TextEngineSurface: /* Flush the renderer so we can draw directly to the window surface */ @@ -212,7 +242,7 @@ static void MoveCursorUp(Scene *scene) x = substring.rect.x; } y = substring.rect.y - fontHeight; - if (TTF_GetTextSubStringAtPoint(scene->text, x, y, &substring)) { + if (TTF_GetTextSubStringForPoint(scene->text, x, y, &substring)) { scene->cursor = GetCursorTextIndex(scene->font, x, &substring); } } @@ -231,7 +261,7 @@ static void MoveCursorDown(Scene *scene) x = substring.rect.x; } y = substring.rect.y + substring.rect.h + fontHeight; - if (TTF_GetTextSubStringAtPoint(scene->text, x, y, &substring)) { + if (TTF_GetTextSubStringForPoint(scene->text, x, y, &substring)) { scene->cursor = GetCursorTextIndex(scene->font, x, &substring); } } @@ -250,26 +280,61 @@ static void SetTextFocus(Scene *scene, bool focused) } else { SDL_StopTextInput(scene->window); } + + /* Reset the highlight */ + scene->highlighting = false; + scene->highlight1 = -1; + scene->highlight2 = -1; } -static void HandleTextClick(Scene *scene, float x, float y) +static void HandleTextMouseDown(Scene *scene, float x, float y) { - TTF_SubString substring; - if (!scene->textFocus) { SetTextFocus(scene, true); return; } /* Set the cursor position */ + TTF_SubString substring; + int textX = (int)SDL_roundf(x - (scene->textRect.x + 4.0f)); + int textY = (int)SDL_roundf(y - (scene->textRect.y + 4.0f)); + if (!TTF_GetTextSubStringForPoint(scene->text, textX, textY, &substring)) { + SDL_Log("Couldn't get cursor location: %s\n", SDL_GetError()); + return; + } + + scene->cursor = GetCursorTextIndex(scene->font, textX, &substring); + scene->highlighting = true; + scene->highlight1 = scene->cursor; + scene->highlight2 = -1; +} + +static void HandleTextMouseMotion(Scene *scene, float x, float y) +{ + if (!scene->highlighting) { + return; + } + + /* Set the highlight position */ + TTF_SubString substring; int textX = (int)SDL_roundf(x - (scene->textRect.x + 4.0f)); int textY = (int)SDL_roundf(y - (scene->textRect.y + 4.0f)); - if (!TTF_GetTextSubStringAtPoint(scene->text, textX, textY, &substring)) { + if (!TTF_GetTextSubStringForPoint(scene->text, textX, textY, &substring)) { SDL_Log("Couldn't get cursor location: %s\n", SDL_GetError()); return; } scene->cursor = GetCursorTextIndex(scene->font, textX, &substring); + scene->highlight2 = scene->cursor; +} + +static void HandleTextMouseUp(Scene *scene, float x, float y) +{ + (void)x; (void)y; + + if (scene->highlighting) { + scene->highlighting = false; + } } static void Cleanup(int exitcode) @@ -491,6 +556,8 @@ int main(int argc, char *argv[]) Cleanup(2); } scene.font = font; + scene.highlight1 = -1; + scene.highlight2 = -1; /* Show which font file we're looking at */ SDL_snprintf(string, sizeof(string), "Font file: %s", argv[0]); /* possible overflow */ @@ -587,7 +654,7 @@ int main(int argc, char *argv[]) { SDL_FPoint pt = { event.button.x, event.button.y }; if (SDL_PointInRectFloat(&pt, &scene.textRect)) { - HandleTextClick(&scene, pt.x, pt.y); + HandleTextMouseDown(&scene, pt.x, pt.y); } else if (scene.textFocus) { SetTextFocus(&scene, false); } else { @@ -599,6 +666,24 @@ int main(int argc, char *argv[]) } break; + case SDL_EVENT_MOUSE_MOTION: + { + SDL_FPoint pt = { event.button.x, event.button.y }; + if (SDL_PointInRectFloat(&pt, &scene.textRect)) { + HandleTextMouseMotion(&scene, pt.x, pt.y); + } + } + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + { + SDL_FPoint pt = { event.button.x, event.button.y }; + if (SDL_PointInRectFloat(&pt, &scene.textRect)) { + HandleTextMouseUp(&scene, pt.x, pt.y); + } + } + break; + case SDL_EVENT_KEY_DOWN: switch (event.key.key) { case SDLK_A: @@ -636,7 +721,19 @@ int main(int argc, char *argv[]) if (scene.textFocus) { /* Copy to clipboard */ if (event.key.mod & SDL_KMOD_CTRL) { - SDL_SetClipboardText(scene.text->text); + int marker1, marker2; + if (GetHighlightExtents(&scene, &marker1, &marker2)) { + size_t length = marker2 - marker1 + 1; + char *temp = (char *)SDL_malloc(length + 1); + if (temp) { + SDL_memcpy(temp, &scene.text->text[marker1], length); + temp[length] = '\0'; + SDL_SetClipboardText(temp); + SDL_free(temp); + } + } else { + SDL_SetClipboardText(scene.text->text); + } } } break; @@ -718,8 +815,24 @@ int main(int argc, char *argv[]) if (event.key.mod & SDL_KMOD_CTRL) { /* Copy to clipboard and delete text */ if (scene.text->text) { - SDL_SetClipboardText(scene.text->text); - TTF_DeleteTextString(scene.text, 0, -1); + int marker1, marker2; + if (GetHighlightExtents(&scene, &marker1, &marker2)) { + size_t length = marker2 - marker1 + 1; + char *temp = (char *)SDL_malloc(length + 1); + if (temp) { + SDL_memcpy(temp, &scene.text->text[marker1], length); + temp[length] = '\0'; + SDL_SetClipboardText(scene.text->text); + SDL_free(temp); + } + TTF_DeleteTextString(scene.text, marker1, (int)length); + scene.cursor = marker1; + scene.highlight1 = -1; + scene.highlight2 = -1; + } else { + SDL_SetClipboardText(scene.text->text); + TTF_DeleteTextString(scene.text, 0, -1); + } } } } @@ -727,8 +840,12 @@ int main(int argc, char *argv[]) case SDLK_LEFT: if (scene.textFocus) { if (event.key.mod & SDL_KMOD_CTRL) { - /* Move to the beginning of the line (FIXME) */ - scene.cursor = 0; + /* Move to the beginning of the line */ + TTF_SubString substring; + if (TTF_GetTextSubString(scene.text, scene.cursor, &substring) && + TTF_GetTextSubStringForLine(scene.text, substring.line_index, &substring)) { + scene.cursor = substring.offset; + } } else { MoveCursorLeft(&scene); } @@ -737,7 +854,12 @@ int main(int argc, char *argv[]) case SDLK_RIGHT: if (scene.textFocus) { if (event.key.mod & SDL_KMOD_CTRL) { - /* Move to the end of the line (FIXME) */ + /* Move to the end of the line */ + TTF_SubString substring; + if (TTF_GetTextSubString(scene.text, scene.cursor, &substring) && + TTF_GetTextSubStringForLine(scene.text, substring.line_index, &substring)) { + scene.cursor = substring.offset + substring.length; + } } else { MoveCursorRight(&scene); } diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h index 61d93e75..b53a6d9c 100644 --- a/include/SDL3_ttf/SDL_ttf.h +++ b/include/SDL3_ttf/SDL_ttf.h @@ -1353,6 +1353,7 @@ typedef struct TTF_Text { char *text; /**< A copy of the text used to create this text object, useful for layout and debugging. This will be freed automatically when the object is destroyed. */ SDL_FColor color; /**< The color of the text, read-write. You can change this anytime. */ + int num_lines; /**< The number of lines in the text, 0 if it's empty */ int refcount; /**< Application reference count, used when freeing surface */ @@ -1696,16 +1697,18 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSize(TTF_Text *text, int *w, int *h) */ typedef struct TTF_SubString { - int offset; /**< The byte offset from the beginning of the text */ - int length; /**< The byte length starting at the offset */ - SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */ + int offset; /**< The byte offset from the beginning of the text */ + int length; /**< The byte length starting at the offset */ + int line_index; /**< The index of the line that contains this substring */ + int cluster_index; /**< The internal cluster index, used for quickly iterating */ + SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */ } TTF_SubString; /** - * Get the portion of a text string that surrounds a text offset. + * Get the substring of a text object that surrounds a text offset. * - * If the offset is less than 0, this will return a zero width substring at - * the beginning of the text. If the offset is greater than or equal to the + * If `offset` is less than 0, this will return a zero width substring at + * the beginning of the text. If `offset` is greater than or equal to the * length of the text string, this will return a zero width substring at the * end of the text. * @@ -1718,6 +1721,38 @@ typedef struct TTF_SubString */ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring); +/** + * Get the substring of a text object that contains the given line. + * + * If `line` is less than 0, this will return a zero width substring at the beginning of the text. If `line` is greater than or equal to `text->num_lines` this will return a zero width substring at the end of the text. + * + * \param text the TTF_Text to query. + * \param line a zero-based line index, in the range [0 .. text->num_lines-1]. + * \param substring a pointer filled in with the substring containing the offset. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substring); + +/** + * Get the substrings of a text object that contain a range of text. + * + * The smaller offset will be clamped to 0 and the larger offset will be clamped to the length of text minus 1. The substrings that are returned will include the first offset and the second offset inclusive, e.g. {0, 2} of "abcd" will return "abc". If the text is empty, this will return a single zero width substring. + * + * If an offset is negative, it will be considered as an offset from the end of the text, so {0, -1} would return substrings for the entire text. + * + * \param text the TTF_Text to query. + * \param offset1 the first byte offset into the text string. + * \param offset2 the second byte offset into the text string. + * \param count a pointer filled in with the number of substrings returned, may + * be NULL. + * \returns a NULL terminated array of substring pointers or NULL on + * failure; call SDL_GetError() for more information. This is a + * single allocation that should be freed with SDL_free() when it is + * no longer needed. + */ +extern SDL_DECLSPEC TTF_SubString ** SDLCALL TTF_GetTextSubStringsForRange(TTF_Text *text, int offset1, int offset2, int *count); + /** * Get the portion of a text string that is closest to a point. * @@ -1728,12 +1763,11 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset * outside the bounds of the text area. * \param y the y coordinate relative to the top side of the text, may be * outside the bounds of the text area. - * \param substring a pointer filled in with the closest substring of text to - * the given point, may be NULL. + * \param substring a pointer filled in with the closest substring of text to the given point. * \returns true on success or false on failure; call SDL_GetError() for more * information. */ -extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringAtPoint(TTF_Text *text, int x, int y, TTF_SubString *substring); +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring); /** * Update the layout of a text object. diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c index 549a2bb9..a0d7f4ac 100644 --- a/src/SDL_ttf.c +++ b/src/SDL_ttf.c @@ -1404,7 +1404,7 @@ static bool Render_Line(const render_mode_t render_mode, int subpixel, TTF_Font #endif } -static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int width, int height, TTF_DrawOperation *ops, int *current_op, TTF_SubString *clusters, int *current_cluster, int cluster_offset) +static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int width, int height, TTF_DrawOperation *ops, int *current_op, TTF_SubString *clusters, int *current_cluster, int cluster_offset, int line_index) { int i; int op_index = *current_op; @@ -1483,6 +1483,7 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w if (offset != last_offset) { cluster = &clusters[cluster_index++]; cluster->offset = cluster_offset + offset; + cluster->line_index = line_index; SDL_copyp(&cluster->rect, &bounds); last_offset = offset; @@ -3636,6 +3637,7 @@ struct TTF_TextLayout { bool wrap; int wrap_length; + int *lines; }; typedef struct TTF_InternalText @@ -3708,15 +3710,28 @@ static int SDLCALL SortClusters(const void *a, const void *b) return (A->offset - B->offset); } -static bool CalculateClusterLengths(TTF_SubString *clusters, int num_clusters, size_t length) +static bool CalculateClusterLengths(TTF_SubString *clusters, int num_clusters, size_t length, int *lines) { SDL_qsort(clusters, num_clusters, sizeof(*clusters), SortClusters); int i; - for (i = 0; i < (num_clusters - 1); ++i) { - clusters[i].length = clusters[i + 1].offset - clusters[i].offset; + int last_line = 0; + for (i = 0; i < num_clusters; ++i) { + TTF_SubString *cluster = &clusters[i]; + cluster->cluster_index = i; + if (i < (num_clusters - 1)) { + cluster->length = clusters[i + 1].offset - cluster->offset; + } else { + cluster->length = (int)(length - cluster->offset); + } + + if (lines) { + if (cluster->line_index != last_line) { + lines[cluster->line_index - 1] = i; + last_line = cluster->line_index; + } + } } - clusters[i].length = (int)(length - clusters[i].offset); return true; } @@ -3755,7 +3770,7 @@ static bool LayoutText(TTF_Text *text) } // Create the text drawing operations - if (!Render_Line_TextEngine(font, xstart, ystart, width, height, ops, &num_ops, clusters, &num_clusters, 0)) { + if (!Render_Line_TextEngine(font, xstart, ystart, width, height, ops, &num_ops, clusters, &num_clusters, 0, 0)) { goto done; } @@ -3768,7 +3783,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); } - if (!CalculateClusterLengths(clusters, num_clusters, length)) { + if (!CalculateClusterLengths(clusters, num_clusters, length, NULL)) { goto done; } @@ -3776,6 +3791,7 @@ static bool LayoutText(TTF_Text *text) done: if (result) { + text->num_lines = 1; text->internal->w = width; text->internal->h = height; text->internal->num_ops = num_ops; @@ -3800,6 +3816,7 @@ static bool LayoutTextWrapped(TTF_Text *text) int num_ops = 0, max_ops = 0, extra_ops = 0, additional_ops; TTF_SubString *clusters = NULL, *new_clusters; int num_clusters = 0, max_clusters = 0, cluster_offset; + int *lines = NULL; bool result = false; if (!GetWrappedLines(font, text->text, length, wrapLength, &strLines, &numLines, &width, &height)) { @@ -3813,6 +3830,16 @@ static bool LayoutTextWrapped(TTF_Text *text) ++extra_ops; } + if (numLines > 1) { + lines = (int *)SDL_malloc((numLines - 1) * sizeof(*lines)); + if (!lines) { + goto done; + } + for (int i = 0; i < (numLines - 1); ++i) { + lines[i] = -1; + } + } + // Render each line for (i = 0; i < numLines; i++) { int xstart, ystart, line_width, xoffset; @@ -3856,7 +3883,7 @@ static bool LayoutTextWrapped(TTF_Text *text) cluster_offset = (int)(strLines[i].text - text->text); // Create the text drawing operations - if (!Render_Line_TextEngine(font, xstart + xoffset, ystart, width, height, ops, &num_ops, clusters, &num_clusters, cluster_offset)) { + if (!Render_Line_TextEngine(font, xstart + xoffset, ystart, width, height, ops, &num_ops, clusters, &num_clusters, cluster_offset, i)) { goto done; } @@ -3870,7 +3897,7 @@ static bool LayoutTextWrapped(TTF_Text *text) } } - if (!CalculateClusterLengths(clusters, num_clusters, length)) { + if (!CalculateClusterLengths(clusters, num_clusters, length, lines)) { goto done; } @@ -3878,15 +3905,18 @@ static bool LayoutTextWrapped(TTF_Text *text) done: if (result) { + text->num_lines = numLines; text->internal->w = width; text->internal->h = height; text->internal->num_ops = num_ops; text->internal->ops = ops; text->internal->num_clusters = num_clusters; text->internal->clusters = clusters; + text->internal->layout->lines = lines; } else { SDL_free(ops); SDL_free(clusters); + SDL_free(lines); } SDL_free(strLines); return result; @@ -4166,17 +4196,14 @@ bool TTF_GetTextSize(TTF_Text *text, int *w, int *h) return true; } -bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring) +bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring) { if (substring) { SDL_zerop(substring); } TTF_CHECK_POINTER("text", text, false); - - if (!substring) { - return true; - } + TTF_CHECK_POINTER("substring", substring, false); if (!TTF_UpdateText(text)) { return false; @@ -4240,18 +4267,193 @@ bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *sub return true; } -bool TTF_GetTextSubStringAtPoint(TTF_Text *text, int x, int y, TTF_SubString *substring) +bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substring) { if (substring) { SDL_zerop(substring); } TTF_CHECK_POINTER("text", text, false); + TTF_CHECK_POINTER("substring", substring, false); + + if (!TTF_UpdateText(text)) { + return false; + } + + if (text->internal->num_clusters == 0) { + substring->rect.h = text->internal->font->height; + return true; + } + + int num_clusters = text->internal->num_clusters; + TTF_SubString *clusters = text->internal->clusters; + if (line < 0) { + SDL_copyp(substring, &clusters[0]); + substring->length = 0; + substring->rect.w = 0; + return true; + } - if (!substring) { + if (line >= text->num_lines) { + SDL_copyp(substring, &clusters[num_clusters - 1]); + substring->offset = (int)SDL_strlen(text->text); + substring->length = 0; + if (TTF_GetFontDirection(text->internal->font) != TTF_DIRECTION_RTL) { + substring->rect.x += substring->rect.w; + } + substring->rect.w = 0; return true; } + int *lines = text->internal->layout->lines; + if (line == 0) { + SDL_copyp(substring, &clusters[0]); + } else { + SDL_copyp(substring, &clusters[lines[line - 1]]); + } + if (line == text->num_lines - 1) { + substring->length = (int)SDL_strlen(text->text) - substring->offset; + } else { + substring->length = clusters[lines[line]].offset - substring->offset; + } + for (int i = substring->cluster_index + 1; i < num_clusters; ++i) { + TTF_SubString *cluster = &clusters[i]; + if (cluster->line_index != line) { + break; + } + SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect); + } + return true; +} + +TTF_SubString **TTF_GetTextSubStringsForRange(TTF_Text *text, int offset1, int offset2, int *count) +{ + if (count) { + *count = 0; + } + + TTF_CHECK_POINTER("text", text, NULL); + + if (!TTF_UpdateText(text)) { + return NULL; + } + + if (text->internal->num_clusters == 0) { + TTF_SubString **result = (TTF_SubString **)SDL_malloc(2 * sizeof(*result) + 1 * sizeof(**result)); + if (!result) { + return NULL; + } + + TTF_SubString *substring = (TTF_SubString *)(result + 2); + result[0] = substring; + result[1] = NULL; + SDL_zerop(substring); + substring->rect.h = text->internal->font->height; + + if (count) { + *count = 1; + } + return result; + } + + int length = (int)SDL_strlen(text->text); + if (offset1 < 0) { + offset1 = length + offset1; + if (offset1 < 0) { + offset1 = 0; + } + } else if (offset1 >= length) { + offset1 = (length - 1); + } + if (offset2 < 0) { + offset2 = length + offset2; + if (offset2 < 0) { + offset2 = 0; + } + } else if (offset2 >= length) { + offset2 = (length - 1); + } + if (offset1 > offset2) { + int tmp = offset1; + offset1 = offset2; + offset2 = tmp; + } + + TTF_SubString substring1, substring2; + if (!TTF_GetTextSubString(text, offset1, &substring1) || + !TTF_GetTextSubString(text, offset2, &substring2)) { + return NULL; + } + + if (substring1.cluster_index == substring2.cluster_index) { + TTF_SubString **result = (TTF_SubString **)SDL_malloc(2 * sizeof(*result) + 1 * sizeof(**result)); + if (!result) { + return NULL; + } + + TTF_SubString *substring = (TTF_SubString *)(result + 2); + result[0] = substring; + result[1] = NULL; + SDL_copyp(substring, &substring1); + + if (count) { + *count = 1; + } + return result; + } + + // Build a list of contiguous substrings + TTF_SubString *clusters = text->internal->clusters; + int num_results = 1; + TTF_SubString *last = &clusters[substring1.cluster_index]; + for (int i = substring1.cluster_index + 1; i <= substring2.cluster_index; ++i) { + TTF_SubString *cluster = &clusters[i]; + if (cluster->line_index != last->line_index) { + ++num_results; + last = cluster; + } + } + + TTF_SubString **result = (TTF_SubString **)SDL_malloc((num_results + 1) * sizeof(*result) + num_results * sizeof(**result)); + if (!result) { + return NULL; + } + + TTF_SubString *substrings = (TTF_SubString *)(result + num_results + 1); + for (int i = 0; i < num_results; ++i) { + result[i] = &substrings[i]; + } + result[num_results] = NULL; + + TTF_SubString *current = substrings; + SDL_copyp(current, &substring1); + for (int i = substring1.cluster_index + 1; i <= substring2.cluster_index; ++i) { + TTF_SubString *cluster = &clusters[i]; + if (cluster->line_index == current->line_index) { + SDL_GetRectUnion(¤t->rect, &cluster->rect, ¤t->rect); + } else { + current->length = (cluster->offset - current->offset); + ++current; + SDL_copyp(current, cluster); + } + } + current->length = (substring2.offset - current->offset) + substring2.length; + + if (count) { + *count = num_results; + } + return result; +} + +bool TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring) +{ + if (substring) { + SDL_zerop(substring); + } + + TTF_CHECK_POINTER("text", text, false); + TTF_CHECK_POINTER("substring", substring, false); + if (!TTF_UpdateText(text)) { return false; } @@ -4308,6 +4510,11 @@ bool TTF_UpdateText(TTF_Text *text) text->internal->clusters = NULL; text->internal->num_clusters = 0; } + if (text->internal->layout->lines) { + SDL_free(text->internal->layout->lines); + text->internal->layout->lines = NULL; + } + text->num_lines = 0; text->internal->w = 0; text->internal->h = 0; diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym index e3fcb32f..5e8efeaa 100644 --- a/src/SDL_ttf.sym +++ b/src/SDL_ttf.sym @@ -45,7 +45,9 @@ SDL3_ttf_0.0.0 { TTF_GetTextFont; TTF_GetTextSize; TTF_GetTextSubString; - TTF_GetTextSubStringAtPoint; + TTF_GetTextSubStringForLine; + TTF_GetTextSubStringForPoint; + TTF_GetTextSubStringsForRange; TTF_GetTextWrapping; TTF_Init; TTF_InsertTextString;