I have a textbox class that works nicely with wider characters such as a,b,c... but with characters like 'f' and 'l' it seems to incorrectly get the sizing of those characters, yet correctly get the sizing of the others? Here is the code for the 'highlighting' of the text for the textbox class, its a bit long ill fix that up later, but should documented enough to understand easily.
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(), ¤tCharacterWidth, ¤tCharacterHeight);
///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, ¤tCharacterRect) ){
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;
}
}
For reference in the font passed in, here is how i obtain it in the text 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);
}
}
and so if i pass in, say 25 as my text size, i have my lowestFontSize = 10 and highestFontSize = 100, so the index i need for size 25 would be (25-10 -1 = 14), as indexing begins at 0, which is that first line before i create my static vector of fonts in the text class. Here is a snippet of what i'm trying to explain:
This is clearly working properly.
But now, it is completely inaccurate. If i select a random character towards the end of the text, it is not correctly highlighted, only from the beginning the first one looks pretty much perfect, but then it seems as if the inaccuracy is compounded, hence making the total grey highlighting much wider than it is supposed to be.
The problem with your method is that individual character widths are meaningless. The renderer adjusts them depending on context (their neighbours in the rendered string). So the width of
i
in the rendered stringbit
is not necessarily the same as the width ofi
in the rendered stringfil
.The method to find text selection coordinates needs to take context into account.
Say we have the width of the three strings:
prefixWidth
is the size of the prefix (the original line of text up to but not including the selection)selWidth
is the width of the selection itselftotalWidth
is the width of the prefix and the selection concatenated(we don't care about the portion after the selection). The first two widths will be close to the third, but will not add up because of the kerning between the last character of the prefix and the first character of the selection. The difference is the correction you need to apply to the X coordinate of the rendered selection. So we need to start rendering selection at the X coordinate which is not
prefixWidth
, butwhich is the same as
so you don't really need to calculate prefixWidth at all.
A full (semi-)working example program below. The arguments are (1) font file full path (2) font size (3) string to render (4) selection start (5) selection length. The selection is rendered on top of the original text, shifted 1 pixel down and right, so it is easy to see if there is any deviation.