Skip to content

[rtext] Use libc for TextLength(), TextCopy(), TextSubtext() and TextInsert() #4911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

williewillus
Copy link
Contributor

@williewillus williewillus commented Apr 27, 2025

No description provided.

if ((src != NULL) && (dst != NULL))
// strcpy is marked restrict, meaning src and dst must not alias.
// Attempt to defend against that, but this is not fully robust
// as someone could pass in two sub-portions of the same string.
Copy link
Contributor Author

@williewillus williewillus Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this is also already a problem with the preexisting code, so we aren't necessarily regressing here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function prototype of TextCopy should probably be marked restrict since strcpy already is. Alternative is to use memmove instead of strcpy to protect against aliasing.

Copy does implicitly signal that two non-overlapping memory regions should be used, however there is risk that some developer has used this function to copy into overlapping memory regions. Strcpy will break such code, whilst memmove will not.

Alternate suggested code:

bytes = strlen(src);
memmove(dst, src, bytes);

@williewillus
Copy link
Contributor Author

williewillus commented Apr 27, 2025

This is a reduced version of #3362 that avoids all the stylistic changes of that PR, and also does not change TextFormat. The minimum possible changes are done to achieve the goal, which is to use libc's string and memory functions as they are often highly-optimized for the current platform versus a naive C approach.

Test Plan:
Wrote a small test program outside of Raylib with my versions of the functions, and things look as I expect:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define MAX_TEXT_BUFFER_LENGTH              1024

unsigned int TextLength(const char *text)
{
    unsigned int length = 0;

    if (text != NULL)
    {
        length = strlen(text);
    }

    return length;
}

int TextCopy(char *dst, const char *src)
{
    int bytes = 0;

    // strcpy is marked restrict, meaning src and dst must not alias.
    // Attempt to defend against that, but this is not fully robust
    // as someone could pass in two sub-portions of the same string.
    if ((src != NULL) && (dst != NULL) && (src != dst))
    {
        strcpy(dst, src);
        bytes = strlen(src);
    }

    return bytes;
}

const char *TextSubtext(const char *text, int position, int length)
{
    static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
    memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);

    int textLength = TextLength(text);

    if (position >= textLength)
    {
        return buffer; //First char is already '\0' by memset
    }

    int maxLength = textLength - position;
    if (length > maxLength) length = maxLength;
    if (length >= MAX_TEXT_BUFFER_LENGTH) length = MAX_TEXT_BUFFER_LENGTH - 1;

    memcpy(buffer, text + position, length);
    buffer[length] = '\0';

    return buffer;
}

char *TextInsert(const char *text, const char *insert, int position)
{
    int textLen = TextLength(text);
    int insertLen = TextLength(insert);

    char *result = (char *)malloc(textLen + insertLen + 1);

    memcpy(result, text, position);
    memcpy(result + position, insert, insertLen);
    memcpy(result + position + insertLen, text + position, textLen - position);

    result[textLen + insertLen] = '\0';     // Make sure text string is valid!

    return result;
}

int main() {
  const char *e = "";
  const char *a = "foo";
  const char *b = "bars";

  printf("%u\n", TextLength(e));
  printf("%u\n", TextLength(a));
  printf("%u\n", TextLength(b));

  char buf[4] = { 0 };
  int ret = TextCopy(buf, a);
  printf("%d %s\n", ret, buf);

  printf("%s\n", TextSubtext(b, 1, 0));
  printf("%s\n", TextSubtext(b, 1, 10));

  char *j = TextInsert(a, b, 2);
  printf("%s\n", j);
  free(j);
  return 0;
}

Result:

0
3
4
3 foo

ars
fobarso

@williewillus williewillus marked this pull request as ready for review April 27, 2025 08:50
@jestarray
Copy link
Contributor

jestarray commented May 4, 2025

awesome, please merge this for 6.0 <3. <string.h> is already used in:

#include <string.h> // Required for: strlen() [Used in ImageTextEx()], strcmp() [Used in LoadImageFromMemory()/LoadImageAnimFromMemory()/ExportImageToMemory()]
anyways

@raysan5 raysan5 changed the title Use libc for TextLength, TextCopy, TextSubtext, and TextInsert [rtext] Use libc for TextLength(), TextCopy(), TextSubtext() and TextInsert() May 7, 2025
@JayLCypher
Copy link
Contributor

Unless there is any specific reason to have the length be of unsigned int (4 bytes), the entire TextLength function could be deprecated since it's literally just manual strlen, missing out on all vectorization and intrinsic functions.

@raysan5
Copy link
Owner

raysan5 commented May 12, 2025

Those functions were created as custom alternatives to libc implementations, also for educational pourposes. There is no plan to replace them by libc implementations. Users wanting libc alternatives can use libc directly.

@raysan5 raysan5 closed this May 12, 2025
@jestarray
Copy link
Contributor

Those functions were created as custom alternatives to libc implementations, also for educational pourposes. There is no plan to replace them by libc implementations. Users wanting libc alternatives can use libc directly.

int size = TextLength(text); // Total size in bytes of the text, scanned by codepoints in loop

int size = TextLength(text); // Get size in bytes of text

I feel like MeasreTextEx and DrawTextEx should use the optimized version of strlen, because I personally call them in the hot gameloop... not to mention <string.h> is already a dependency of rtext.c

@raysan5
Copy link
Owner

raysan5 commented May 14, 2025

@jestarray I'm reviewing those ones...

EDIT: Sorry, I'm seeing that it will break consistency, TextLength() is actually used all around, no strlen(). Let me see if I can address it in some way, maybe with a flag.

@JayLCypher
Copy link
Contributor

JayLCypher commented May 14, 2025

@jestarray I'm reviewing those ones...

EDIT: Sorry, I'm seeing that it will break consistency, TextLength() is actually used all around, no strlen(). Let me see if I can address it in some way, maybe with a flag.

You can flag the function as a const function pointer, but you may have to break a little ABI a bit since you used int as return value for measureText. Example: https://godbolt.org/z/jsG4zqs3b

Alternatively you can skirt with UB by casting the pointer to strlen as (int (*)(const char *)&strlen.
I'm not sure if return value is mentioned in the calling of a function by pointer, but the args are the same size in any case.

Because of the C standard, you can bind the example to STDC_VERSION since strlen is a guarantee by libc, which is guaranteed by standard, if that's wanted.
Some compilers (Clang) will replace known naïve strlen implementations with strlen at compile time as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants