Finding the x,y location of a word when text wrapped

Is there a way to find the x,y location of a word in a string where the text is wrapped?

I’ve made a simple example to illustrate my question. How would I go about finding the location of the first word in the third sentence, “Integer”?

I see there are a number of ways to find the length of a word and the text height (ascent?). So do I need to do some kind of fancy cumulative length calculation on the string up until the word I’m looking for?

Thanks for any suggestions or advice.

2 Likes

Have you experimented with the textToPoints() example here?

You may be able to use the length of the array of points from the substring that precedes "Integer", in order to locate the index of the first point in "Integer". I haven’t tried that, but it might be a good way to start.

2 Likes

OR you write your own class, example in processing java, not p5.js

// Text box (to replace text() with 5 parameters) with wrapping, so that each word can be separately detected when mouse is over 
// click mouse on a word to get the word via println or hover mouse on a word for status bar. 
// Sketch can be strongly improved 

// https://discourse.processing.org/t/finding-the-x-y-location-of-a-word-when-text-wrapped/38243

final String  txt_copy = "Nullam feugiat mauris vitae sapien tincidunt iaculis. Nam malesuada varius porta. Integer sed dolor tempor, tristique nibh quis, vehicula est.";

PFont myFont;

// core idea: the class
TextBox textBox;  

// -------------------------------------------------------------------

void setup() {
  size(400, 400);
  background(0);

  myFont = createFont("Georgia", 32);
  textFont(myFont);
  textSize(20);

  // core idea: fill the class
  textBox = defineTextBox(txt_copy, 29, 47, 
    350, 900);
}//func 

void draw() {
  background(0);

  // core idea: USE the class
  textBox.show();
  textBox.showMouseOver();
}//func 

// -------------------------------------------------------------------
// Inputs 

void mousePressed() {
  println(textBox.getWordUnderMouse());
}//func 

// -------------------------------------------------------------------
// Tools

TextBox defineTextBox(String str_, 
  float x_, float y_, 
  float w_, float h_) {

  // no output, just prepare the object derived from class TextBox.
  // float h_ not in use. 
  // assuming textWrap(WORD);

  // define box 
  TextBox textBoxLocal = new TextBox(); 

  // fill box
  textBoxLocal.xTextBox = x_;
  textBoxLocal.yTextBox = y_;

  String[] stringArray = split ( str_, " " );   //  String[] s1 = splitTokens ( str_, " ,.!?;:" ); 

  String currentLine=""; 
  int wordCount=0;

  // Loop over array
  for (String currentWord : stringArray) {
    String previousVersion = currentLine; 
    currentLine = currentLine + " " + currentWord; 
    currentLine = currentLine.trim(); 

    float cw = textWidth(currentLine);

    // When too long 
    if (cw > w_-32) {
      textBoxLocal.listOfLines.add(previousVersion); 
      // reset
      previousVersion=""; 
      currentLine=currentWord;
    }//if

    // When end of Array  
    if (wordCount == stringArray.length-1) {
      textBoxLocal.listOfLines.add(trim(currentLine));
      break;
    }//if
    //
    wordCount++;
    //
  }//for
  //
  return textBoxLocal; 
  //
}//func 

// ========================================================================

class TextBox {

  final float textSpacing = 33; 

  ArrayList <String> listOfLines = new ArrayList();
  float xTextBox; 
  float yTextBox; 

  // no constr 

  void show() {
    // show Text 
    int lineCount=0; 
    for (String s1 : listOfLines) {
      //output
      text (s1, 
        xTextBox, yTextBox + lineCount * textSpacing);
      lineCount++;
    }
  }

  void showMouseOver() {
    // hover mouse on a word for status bar. 
    // get word 
    String result = getWordUnderMouse(); 
    // status bar 
    showStatusBar(result);
  }//method

  String getWordUnderMouse() {
    // core idea: find word under mouse in ArrayList list

    // find Line from mouseX ---

    String resultingLine="";
    int i=0; 
    for (String s1 : listOfLines) {
      if (mouseY > yTextBox + ((i-1) * textSpacing) && 
        mouseY < yTextBox + (i * textSpacing)) {
        resultingLine=s1;
        break;
      }//if
      i++;
    }//for

    // find word from mouseY ---

    String[] stringArray = split (resultingLine, " ");
    String resultingWord="";
    String currentLine=""; 

    // loop over words in this line 
    for (String currentWord : stringArray) {

      // previous textWidth
      float lineWidth1 = xTextBox+textWidth(currentLine);

      currentLine+= " " + currentWord; 
      // new textWidth
      float lineWidthNew = xTextBox+textWidth(currentLine);

      if (mouseX > lineWidth1 && 
        mouseX < lineWidthNew) {
        resultingWord=currentWord;
        break;
      }//if
    } //for 

    // return ---
    resultingWord=trim(resultingWord); 
    return resultingWord;
  }// func 

  void showStatusBar (String s1_) {
    // status bar 
    noStroke(); 
    fill(111);
    rect(0, height-27, 
      width, 111);

    fill(255); 
    text(s1_, 9, height-6);
  }//method 
  //
}//class
//

3 Likes

This fantastic. Thank you so much @Chrisir!
I did not expect a fully functional answer. Many thanks. I’ve been noodling on how to do this for a while.
Thank you :pray:

3 Likes

thank you! :smiling_face_with_three_hearts:

new version, minor changes

// Text box (to replace text() with 5 parameters) with wrapping, so that each word can be separately detected when mouse is over. 
// Click mouse on a word to get the word via println or hover mouse on a word for the small status bar.

// Sketch can be strongly improved 

// https://discourse.processing.org/t/finding-the-x-y-location-of-a-word-when-text-wrapped/38243

final String  txt_copy = "Nullam feugiat mauris vitae sapien tincidunt iaculis. Nam malesuada varius porta. Integer sed dolor tempor, tristique nibh quis, vehicula est.";

PFont myFont;

// core idea: the class
TextBox textBox;  

// -------------------------------------------------------------------

void setup() {
  size(400, 400);
  background(0);

  myFont = createFont("Georgia", 20);
  textFont(myFont);
  textSize(20);

  // core idea: fill the class
  textBox = new TextBox(txt_copy, 29, 47, 
    350, 300);
}//func 

void draw() {
  background(0);

  // core idea: USE the class
  textBox.show();
  showMouseOver();
}//func 

// -------------------------------------------------------------------
// Inputs 

void mousePressed() {
  // core idea: USE the class
  println(textBox.getWordUnderMouse());
}//func 

// -------------------------------------------------------------------
// Tools

void showMouseOver() {
  // hover mouse on a word for status bar. 
  // get word 
  String result = textBox.getWordUnderMouse();   // core idea: USE the class
  // status bar 
  showStatusBar(result);
}//func

void showStatusBar (String text_) {
  // status bar 
  noStroke(); 
  fill(111);
  rect(0, height-27, 
    width, 111);

  fill(255); 
  text(text_, 9, height-6);
}//func 

// =================================================================

class TextBox {

  final float textSpacing = 23; 

  ArrayList<String> listOfLines = new ArrayList();

  float xTextBox, yTextBox, 
    wTextBox, hTextBox; 

  // constr
  TextBox (String str_, 
    float x_, float y_, 
    float w_, float h_) {

    // no output, just prepare the object derived from class TextBox.
    // float h_ not in use. 
    // assuming textWrap(WORD);

    // fill box
    xTextBox = x_;
    yTextBox = y_;
    wTextBox = w_;
    hTextBox = h_;

    defineListOfLines(str_, w_);
  } // constr

  void defineListOfLines(String str_, float w_) {
    // Helper for the constr 

    String[] stringArray = split(str_, " " );   //  String[] s1 = splitTokens ( str_, " ,.!?;:" ); 

    String currentLine=""; 
    int wordCount=0;

    // Loop over array
    for (String currentWord : stringArray) {
      String previousVersion = currentLine; 
      currentLine = currentLine + " " + currentWord; 
      currentLine = currentLine.trim(); 

      float cw = textWidth(currentLine);

      // When too long 
      if (cw > w_-5) {
        listOfLines.add(previousVersion); 
        // reset
        previousVersion=""; 
        currentLine=currentWord;
      }//if

      // When end of Array  
      if (wordCount == stringArray.length-1) {
        listOfLines.add(trim(currentLine));
        break;
      }//if
      //
      wordCount++;
      //
    }//for
  }

  void show() {
    // show Text 

    /*
     //show box
     noFill();
     stroke(255, 0, 0); 
     rect(xTextBox, yTextBox, 
     wTextBox, hTextBox   );
     */

    fill(255); 
    int lineCount=1; // start with 1 to move text one line down, so it's inside the rectangle  
    for (String s1 : listOfLines) {
      //output
      text (s1, 
        xTextBox, yTextBox + lineCount * textSpacing);
      lineCount++;
    }//for
  }//method

  String getWordUnderMouse() {
    // core idea: find word under mouse in ArrayList list

    // find Line from mouseX ---

    String resultingLine="";
    int lineCount=1; // start with 1 to move text one line down  

    for (String currentLine : listOfLines) {
      if (mouseY > yTextBox + (lineCount-1) * textSpacing && 
        mouseY < yTextBox + lineCount * textSpacing) {
        resultingLine=currentLine; //found
        break;
      }//if
      lineCount++;
    }//for

    // find word from mouseY ---

    String[] stringArray = split (resultingLine, " ");
    String resultingWord="";
    String currentLine=""; 

    // loop over words in this line 
    for (String currentWord : stringArray) {

      // previous textWidth
      float lineWidthOld = xTextBox+textWidth(currentLine);

      currentLine+= " " + currentWord; 
      // new textWidth
      float lineWidthNew = xTextBox+textWidth(currentLine);

      if (mouseX > lineWidthOld && 
        mouseX < lineWidthNew) {
        resultingWord=currentWord; //found
        break;
      }//if
    } //for 

    // return result ---
    // removing . and , etc. at the end of the word 
    final String deleteCharacters=" ,.!?;:";
    resultingWord = removeCharsFromString(resultingWord, deleteCharacters);  
    return resultingWord;
  } // method 

  String removeCharsFromString ( String in_, String remove_) {
    // removing . and , etc. at the end of the word 
    for (char currentChar : remove_.toCharArray()) {
      in_=in_.replace(currentChar, ' ');
    }//for 
    in_=trim(in_);
    return in_;
  }//method
  //
}//class
//

3 Likes