Drawing variable string to be multiline and centered on each line

I am certainly a beginner with openFrameworks and coding with C++ in general (have been trying to watch Youtube tutorials non-stop) so apologies if this doesn’t make sense or doesn’t provide the information that you need to actually help me.

I am trying to allow multilines of text (after pressing the return key) to be centered on each line. I have figured out how to allow for multiple lines:

else if(key == OF_KEY_RETURN){
        str += "\n";
    }

However, when the return is pressed, the text goes to the left edge of whatever the longest line is. In short, it is as if each line is grouped and centered to the one that is the longest.

The method I am using to center it is this:

font.drawString(str, ofGetWidth() / 2 - font.stringWidth(str) / 2, ofGetHeight() / 2 - font.stringHeight(str) / 2);

Is there another way to center lines vertically and horizontally that would allow for each line to be centered to the window, rather than all of the lines (grouped together) being centered?

Here are some quick screen captures showing this.

What I am currently doing (but don’t want):
https://www.screencast.com/t/p0rty2gDAyk

What I want to do but am unable to achieve via openFrameworks (this was recorded from Adobe InDesign):
https://www.screencast.com/t/2qxl38b7gN

Is this possible without addons? I did find one addon, ofxParagraph, but that seems to be out of date and I was unable to get it to actually run as I was wanting. There wouldn’t be any errors, but it wouldn’t register what I was typing.

Thanks all for your help! Let me know if I can answer any questions that may clarify any confusion

Hi, that behavior is normal as it renders the whole string of text and not line by line.
There are a few addons out there. I recommend you ofxFontStash2 which is up to date and works really well.

if you dont want to use it you need to draw each line of text independently

you can either have a vector of strings and add a new one on each press or return or simple parse and render each during draw. The former is more efficient, the latter is easier. Code for the latter follows.

auto lines = ofSplitString(str, "\n");
float y = (ofGetHeight() - font.stringHeight(str))/2 + font.getAscenderHeight();

for(auto& l : lines){
font.drawString(l, ofGetWidth() / 2 - font.stringWidth(l) / 2,  y);

y+= font.getLineHeight();
}

Thanks for the response! I am wanting to keep it all as one text string if possible. That way, the text can be deleted with the backspace key (jumping up to the previous line when the current line is entirely deleted). That wouldn’t be possible if it was split into individual strings, right?

I’ll check out ofxFontStash2, thanks for linking that here. Hopefully I can struggle my way through it and eventually get there lol

you still can. so when pressing the backspace key check if the current (the last of the lines) is empty, and if it is remove it from the vector.

should be something like

//vector<string> lines;// declared like this elswere. use the name of your vector instead
if(lines.back().empty()) lines.pop_back();

I did some research and wasn’t able to determine how to utilize vector strings in this sense. It seemed easier to use ofxFontStash2 which I was able to get to work for what I needed! (for the most part)

The only issue I am facing now is that I can’t seem to figure out vertical centering of the paragraph. For the string I was using originally, I was using this to make it vertically centered:

ofGetHeight() / 2 - font.stringHeight(Text) / 2);

I am able to get a one-line string line centered using just ofGetHeight()/2, but I can’t figure out how to reference the text box’s height in order to then subtract half of it, keeping multiline paragraphs centered.

The rest of my project is:

void ofApp::setup(){
    Text = "";
    
    fonts.setup(true);
    
    fonts.addFont("Montserrat", "Montserrat-SemiBold.ttf");
    
    fonts.addStyle("body", ofxFontStash2::Style("Montserrat", 32, ofColor::white));

}

void ofApp::draw(){
    
    float margin = ofGetWidth()/4;
    float x = margin;
    float y = ofGetHeight()/2;
    float colW = ofGetWidth()/2;
    
    //location of paragraph start point
    drawInsertionPoint(x, y, 100);
    
    //draw text box
    ofRectangle bbox;
    bbox = fonts.drawFormattedColumn(Text, x, y, colW, OF_ALIGN_HORZ_CENTER, debug);
    
    //draw left & right column limits
    ofSetColor(255,32);
    ofDrawLine(x , 0, x, ofGetHeight());
    ofDrawLine(x + colW, 0, x + colW, ofGetHeight());

}

void ofApp::keyPressed(int key){
    if (key == 8 && Text.size() > 0) { //backspace
        Text = Text.substr(0, Text.size()-1); //delete one character

    } else if(key == OF_KEY_RETURN){
        Text += "\n";

    } else {
        Text.append(1, (char)key);

}

void ofApp::drawInsertionPoint(float x, float y, float w){

}

How can I reference the height of “bbox” in order to use that in calculating the “y” of the drawInsertionPoint?

Thanks again for your help!!

Hi, I dont know how ofxFontStash handles either the bounding box or the drawing point.
At least when using ofTrueTypeFont, the y cordinate you pass when drawing refers to the baseline of the text. So if you want to center it you have to take that into account. if you get the bounding box of the text, but pass to it 0,0 as the cordinates, then you will get in the boxes y the value from the baseline to the top of the letterss. using that you can draw it centered.

the following should give you the y position to draw a text vertically centered.

center.y  + (font.getAscenderHeight()  + font.getDescenderHeight()) *0.5;
1 Like