TTF_SizeText() 未在 SDL2、C++ 中返回 'i' 'j' 和“1”(目前已发现)的正确值

TTF_SizeText() not returning correct values for 'i' 'j' and '1' (so far discovered) in SDL2, C++

我有一个文本框 class 可以很好地处理更宽的字符,例如 a、b、c... 但是对于 'f' 和 'l' 这样的字符,它似乎不正确这些字符的大小,但正确地得到其他人的大小?这是文本框 class 文本的 'highlighting' 的代码,它有点长,我稍后会修复它,但应该有足够的文档以便于理解。

void Textbox::Highlight_Text(SDL_Renderer *renderer)
{
    if (clickedOn == true){


        int currentCharacterWidth = 0;
        int currentCharacterHeight = 0;
        int totalSize = 0;
        SDL_Rect currentCharacterRect;
        string currentCharacter, tempText;



        if (highlightedCharacters.size() >= 1){ ///To make sure only 1 thing is highlighted, in conjunction with next part
            highlighted = true;
        }

        if (highlighted == true){   /// if a part is highlighted, and is left highlighted, next time clicked, remove the highlighting and redo it
            if (EVENTS.mouseClicked == false){
                resetHighlightingNextClick = true;
            }
        }

        if (resetHighlightingNextClick == true){
            if (highlighted == true){
                if (EVENTS.mouseClicked == true){       ///actually remove the highlighting
                    highlightedCharacters.clear();
                    indexOfCharactersHighlighted.clear();
                    highlighted = false;
                    resetHighlightingNextClick = false;
                }
            }
        }



        for (int i=0; i < textboxText.Get_Text().size(); i++){
            currentCharacter =  textboxText.Get_Text()[i];
            TTF_SizeText(textboxText.fonts[textboxText.fontIndex], currentCharacter.c_str(), &currentCharacterWidth, &currentCharacterHeight);

            ///the totalSize added to rectangle is not making it wider, its adjusting its x value offset
            currentCharacterRect = {textboxText.x + totalSize, textboxText.y + int(textboxText.textSize*0.1), currentCharacterWidth, currentCharacterHeight};
            totalSize += currentCharacterWidth; ///"current" size of text in loop to get x value of specific character clicked on

            ///If mouse is touching any of the characters in the text
            if ( SDL_PointInRect(&EVENTS.mousePos, &currentCharacterRect) ){
                EVENTS.Change_Cursor(SDL_SYSTEM_CURSOR_IBEAM);

                if (EVENTS.mouseClicked == true){   ///Clicking on the text to highlight
                    if (In_Array(highlightedCharacters, currentCharacterRect.x) == false  ){
                        highlightedCharacters.push_back(currentCharacterRect);  ///If there is no duplicates
                        indexOfCharactersHighlighted.push_back(i); ///Get index of text being highlighted, its always in order too

                    }

                    if (  currentCharacterRect.x != highlightedCharacters[highlightedCharacters.size()-1].x){ ///So they don't stack up highlights, ie, you can remove them
                        /// If the mouse is not highlighting the last one, say second last on the right for example, delete the one in front of it (last one)
                        ///Like when highlighting text with mouse, it adapts to how you move it, so it unhighlights text not being highlighted
                        highlightedCharacters.pop_back();
                        indexOfCharactersHighlighted.pop_back();

                    }
                }

            }

        }///End for loop




        if (highlighted == true ){
            if (EVENTS.backspacePressed == true || EVENTS.currentKey != ""){
                tempText = textboxText.Get_Text();

                ///remove highlighted characters
                if (indexOfCharactersHighlighted.size() != 0){
                    ///the range of values highlighted will always be in a sorted order
                    tempText.erase( Min(indexOfCharactersHighlighted)  , Max(indexOfCharactersHighlighted)-Min(indexOfCharactersHighlighted)+1  );  ///erase the range of values highlighted
                    textboxText.Change_Text(renderer, tempText);

                    ///once removed text, clear every highlighted related thing
                    highlightedCharacters.clear();
                    indexOfCharactersHighlighted.clear();
                    highlighted = false;
                    resetHighlightingNextClick = false;

                    EVENTS.backspacePressed = false;
                    EVENTS.currentKey = "";
                }

            }
        }



    }   ///End if for clicked on



    ///fit with scrolling offsets
    if (EVENTS.scrolled == true){
        for (int p=0; p < highlightedCharacters.size(); p++){
            highlightedCharacters[p].y += EVENTS.scrollVal;
        }
    }




    ///Drawing the highlighted text
    if (highlighted == true   &&   clickedOn == true){
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer, 55,60,65, 75);
        for (int j=0; j < highlightedCharacters.size(); j++){
            SDL_RenderFillRect(renderer, &highlightedCharacters[j]);
        }
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
    }


    ///when clicked off textbox, clear everything/highlighting
    if (clickedOn == false){
        highlightedCharacters.clear();
        indexOfCharactersHighlighted.clear();
        highlighted = false;
    }



}

在传入的字体中参考,这里是我在文本中的获取方式class

    fontIndex = textSize-lowestFontSize  -1;

    ///One time setups
    if (numOfInstances == 1){
        try{
            TTF_Init();
            //cout << "Initialised ttf" << endl;
        }
        catch (exception &err){
            cout << "Could not initialise ttf for text \"" << text << "\". Error from SDL is: " << TTF_GetError() << ". Error from C++ is: " << err.what() << endl;
        }

        for (int i=lowestFontSize; i <= highestFontSize; i++){
            TTF_Font *currentFont = TTF_OpenFont(fontType.c_str(), i);
            if (!currentFont){
                cout << "Error with font in text \"" << txt << "\" Error is: " << SDL_GetError() << endl;
            }

            fonts.push_back(currentFont);
        }

    }

所以如果我传入,假设 25 作为我的文本大小,我的 lowestFontSize = 10 和 highestFontSize = 100,所以我需要的大小 25 的索引将是 (25-10 -1 = 14),因为索引从 0 开始,这是我在文本 class 中创建静态字体矢量之前的第一行。这是我要解释的片段:

这显然工作正常。

但是现在,完全不准确了。如果我 select 文本末尾的一个随机字符,它没有正确突出显示,只有从一开始第一个看起来非常完美,但似乎不准确是复合的,因此使总灰色突出显示比预期的要宽得多。

你的方法的问题是单个字符的宽度没有意义。渲染器根据上下文(它们在渲染字符串中的邻居)调整它们。所以i在渲染字符串bit中的宽度不一定和i在渲染字符串fil.

中的宽度相同

查找文本选择坐标的方法需要考虑上下文。

假设我们有三个字符串的宽度:

  • prefixWidth是前缀的大小(原始文本行到但不包括选择)
  • selWidth是选区本身的宽度
  • totalWidth是前缀宽度和选择串接

(我们不关心选择后的部分)。前两个宽度将接近第三个,但不会加起来,因为前缀的最后一个字符和所选内容的第一个字符之间的字距调整。不同之处在于您需要对渲染选区的 X 坐标应用校正。所以我们需要在不是prefixWidth而是

的X坐标处开始渲染选区
prefixWidth + (totalWidth - (prefixWidth + selWidth))

相同
totalWidth - selWidth

所以你根本不需要计算 prefixWidth。

下面是一个完整的(半)工作示例程序。参数是 (1) 字体文件完整路径 (2) 字体大小 (3) 要呈现的字符串 (4) 选择开始 (5) 选择长度。选区是在原文之上渲染的,向下和向右移动1个像素,所以很容易看出是否有任何偏差。

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

char* substr(const char* src, int fromChar, int subLen)
{
    if (subLen < 0 || fromChar < 0 || fromChar + subLen > (int)strlen(src))
    {
        fprintf (stderr, "invalid substring\n");
        exit (EXIT_FAILURE);
    }

    char* z = (char*)malloc(subLen);
    strncpy(z, src + fromChar, subLen);
    z[subLen] = '[=12=]';
    return z;
}

void textExtent(TTF_Font* font, const char* text,
                int fromChar, int subLen, int* w, int* h)
{
    int l = strlen(text);
    if (subLen == -1) subLen = l;
    if (fromChar < 0 || subLen > l)
    {
        fprintf (stderr, "Bad text extent\n");
        exit (EXIT_FAILURE);
    }

    char* z = substr(text, fromChar, subLen);

    TTF_SizeUTF8(font, z, w, h);

    free(z);
}

int textWidth(TTF_Font* font, const char* text,
    int fromChar, int subLen)
{
    int w, h;
    textExtent(font, text, fromChar, subLen, &w, &h);
    return w;
};

int main(int argc, char ** argv)
{
    bool quit = false;
    SDL_Event event;

    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();

    if (argc != 6)
    {
        fprintf (stderr, "usage: %s font text from length\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    const char* fontpath = argv[1];
    int fontSz = atoi(argv[2]);
    const char* txt = argv[3];
    int from = atoi(argv[4]);
    int len = atoi(argv[5]);

    int tsize = strlen(txt);
    if (from < 0 || from + len >= tsize)
    {
        fprintf (stderr, "Invalid text portion to highlight\n");
        exit (EXIT_FAILURE);
    }

    if (fontSz < 2 || fontSz > 300)
    {
        fprintf (stderr, "Invalid font size\n");
        exit (EXIT_FAILURE);
    }

    // open font to render with

    TTF_Font * font = TTF_OpenFont(fontpath, fontSz);
    if (!font)
    {
        fprintf (stderr, "Could not open font %s\n", fontpath);
        exit (EXIT_FAILURE);
    }

    // Query text size
    int textW, textH;
    textExtent(font, txt, 0, -1, &textW, &textH);

    SDL_Window * window = SDL_CreateWindow("SDL_ttf in SDL2",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, textW, textH, 0);

    // Query selection coords
    //
    int selWidth = textWidth(font, txt, from, len);
    int totalWidth = textWidth(font, txt, 0, from+len);

    // Render portions of text

    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_Color color = { 255, 128, 0, 0 };
    SDL_Surface * surface = TTF_RenderUTF8_Blended(font, txt, color);
    SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
    SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);

    SDL_Rect dstrect = { 0, 0, textW, textH };

    SDL_Color color2 = { 0, 128, 255, 128 };

    char* s = substr(txt, from, len);
    SDL_Surface * surface2 = TTF_RenderUTF8_Blended(font, s, color2);
    free(s);
    SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
    SDL_Texture * texture2 = SDL_CreateTextureFromSurface(renderer, surface2);
    SDL_SetTextureAlphaMod(texture2, 128);
    SDL_SetTextureBlendMode(texture2, SDL_BLENDMODE_BLEND);
    SDL_Rect dstrect2 = {totalWidth - selWidth + 1, 1, selWidth, textH };

    while (!quit)
    {
        SDL_WaitEvent(&event);

        switch (event.type)
        {
            case SDL_QUIT:
                quit = true;
                break;
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
        SDL_RenderClear(renderer);

        SDL_RenderCopy(renderer, texture, NULL, &dstrect);
        SDL_RenderCopy(renderer, texture2, NULL, &dstrect2);

        //Update screen
        SDL_RenderPresent(renderer);

    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
    TTF_CloseFont(font);
    TTF_Quit();
    SDL_Quit();

    return 0;
}