void GLHelper::drawTextBox(const std::string& text, const Position& pos, const double layer, const double size, const RGBColor& txtColor, const RGBColor& bgColor, const RGBColor& borderColor, const double angle, const double relBorder, const double relMargin) { if (!initFont()) { return; }; if (bgColor.alpha() != 0) { const double boxAngle = 90; const double stringWidth = size / myFontSize * fonsTextBounds(myFont, 0, 0, text.c_str(), nullptr, nullptr); const double borderWidth = size * relBorder; const double boxHeight = size * (0.32 + 0.6 * relMargin); const double boxWidth = stringWidth + size * relMargin; glPushMatrix(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glTranslated(pos.x(), pos.y(), layer); glRotated(-angle, 0, 0, 1); Position left(-boxWidth * 0.5, 0); setColor(borderColor); drawBoxLine(left, boxAngle, boxWidth, boxHeight); left.add(borderWidth * 1.5, 0); setColor(bgColor); glTranslated(0, 0, 0.01); drawBoxLine(left, boxAngle, boxWidth - 3 * borderWidth, boxHeight - 2 * borderWidth); glPopMatrix(); } drawText(text, pos, layer + 0.02, size, txtColor, angle); }
ofRectangle ofxFontStash2::getTextBounds( const string &text, const ofxFontStashStyle &style, const float x, const float y ){ applyStyle(style); float bounds[4]={0,0,0,0}; int advance = fonsTextBounds( fs, x*pixelDensity, y*pixelDensity, text.c_str(), NULL, bounds ); advance/=pixelDensity; bounds[0]/=pixelDensity; bounds[1]/=pixelDensity; bounds[2]/=pixelDensity; bounds[3]/=pixelDensity; // here we use the "text advance" instead of the width of the rectangle, // because this includes spaces at the end correctly (the text bounds "x" and "x " are the same, // the text advance isn't). return ofRectangle(bounds[0],bounds[1],advance,bounds[3]-bounds[1]); }
int Asset_TextWidth(AssetHandle assetHandle, const char *string, float scale) { Asset *asset = Asset_Get(ASSET_ANY, assetHandle); assert(asset != nullptr); if (asset->type != ASSET_BITMAPFONT && asset->type != ASSET_FONT) { Con_Errorf(ERR_GAME, "asset %s not font or bmpfont", asset->name); return -1; } int hnd; if (asset->type == ASSET_BITMAPFONT) { BitmapFont_t *fnt = (BitmapFont_t*)asset->resource; hnd = fnt->hnd; } else { TTFFont_t *fnt = (TTFFont_t*)asset->resource; hnd = fnt->hnd; } fonsSetFont(ctx, hnd); fonsSetSize(ctx, scale); return (int) fonsTextBounds(ctx, 0, 0, string, nullptr, nullptr); }
void LikertHCI::initializeText(int threadId, FONScontext* fs, int fontNormal, int lineCount, float border, std::shared_ptr<GLSLProgram> shader, float textSize, const std::vector<std::string> &texts, std::string texKey, std::vector<std::vector<std::shared_ptr<Texture> > > &textures, std::vector<std::vector<glm::dvec2> > &sizes) { float sx, sy, lh = 0; unsigned int white = glfonsRGBA(255,255,255,255); unsigned int gray = glfonsRGBA(81, 76, 76, 255); fonsClearState(fs); fonsSetSize(fs, textSize); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); fonsSetAlign(fs, FONS_ALIGN_CENTER | FONS_ALIGN_TOP); fonsVertMetrics(fs, NULL, NULL, &lh); for(int i=0; i < texts.size(); i++) { std::stringstream ss(texts[i]); std::string line; std::vector<std::string> lines; float maxWidth = 0; while(std::getline(ss, line, '\n')){ float width = fonsTextBounds(fs, line.c_str(), NULL, NULL); if (width > maxWidth) { maxWidth = width; } lines.push_back(line); } sx = maxWidth + (1.0f*border); sy = border; if (lineCount > lines.size()) { sy += lh * (lineCount - lines.size()) * 0.5; } float height = lh*lineCount+2.0*border; std::shared_ptr<Texture> depthTexture = Texture::createEmpty("depthTex", sx, height, 1, 1, false, GL_TEXTURE_2D, GL_DEPTH_COMPONENT32F); depthTexture->setTexParameteri(GL_TEXTURE_MIN_FILTER, GL_LINEAR); depthTexture->setTexParameteri(GL_TEXTURE_MAG_FILTER, GL_LINEAR); depthTexture->setTexParameteri(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); depthTexture->setTexParameteri(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture->getID(), 0); textures[threadId][i] = Texture::createEmpty("colorTex", sx, height, 1, 4, false, GL_TEXTURE_2D, GL_RGBA8); textures[threadId][i]->setTexParameteri(GL_TEXTURE_MIN_FILTER, GL_LINEAR); textures[threadId][i]->setTexParameteri(GL_TEXTURE_MAG_FILTER, GL_LINEAR); textures[threadId][i]->setTexParameteri(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); textures[threadId][i]->setTexParameteri(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); textures[threadId][i]->setTexParameterf(GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0); glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textures[threadId][i]->getID(), 0); GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); switch(status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: assert(false); break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: assert(false); break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: assert(false); break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: assert(false); break; case GL_FRAMEBUFFER_UNSUPPORTED: assert(false); break; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: assert(false); break; default: break; } assert(status == GL_FRAMEBUFFER_COMPLETE); sizes[threadId].push_back(glm::dvec2(sx, height)); glViewport(0,0, sx, height); glClear(GL_COLOR_BUFFER_BIT); shader->setUniform("projection_mat", glm::ortho(0., (double)sx, (double)height, 0., -1., 1.)); shader->setUniform("view_mat", glm::dmat4(1.0)); shader->setUniform("model_mat", glm::dmat4(1.0)); shader->setUniform("has_lambertian_texture", false); glDisable(GL_DEPTH_TEST); // Draw the darker interior quad so we get a white border around the outside from the clear color float rim = border/4.0f; GLfloat vertices[] = {rim, height-rim, sx-rim, height-rim, rim, rim, sx-rim, rim}; GLfloat texCoords[] = { 0, 1, 1, 1, 1, 0, 0, 0 }; GLfloat colors[] = { 0.32, 0.3, 0.3, 1.0, 0.32, 0.3, 0.3, 1.0, 0.32, 0.3, 0.3, 1.0, 0.32, 0.3, 0.3, 1.0}; // create the vao GLuint vaoID = 0; glGenVertexArrays(1, &vaoID); glBindVertexArray(vaoID); GLuint quadVBO = 0; glGenBuffers(1, &quadVBO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)+sizeof(texCoords)+sizeof(colors), 0, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // copy vertices starting from 0 offest glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(texCoords), texCoords); // copy texCoords after vertices glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices)+sizeof(texCoords), sizeof(colors), colors); // copy colours after normals // set up vertex attributes glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)sizeof(vertices)); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(vertices)+sizeof(texCoords))); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); glDeleteBuffers(1, &quadVBO); glDeleteVertexArrays(1, &vaoID); shader->setUniform("has_lambertian_texture", true); glActiveTexture(GL_TEXTURE0); shader->setUniform("lambertian_texture", 0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for(int j=0; j < lines.size(); j++) { std::string text = boost::replace_all_copy(lines[j], "_", " "); fonsDrawText(fs, sx/2.0, sy, text.c_str(), NULL); sy += lh; } glDisable(GL_BLEND); textures[threadId][i]->generateMipMaps(); std::string textureKey = texKey + texts[i]; texMan->setTextureEntry(threadId, textureKey, textures[threadId][i]); depthTexture.reset(); } }
const vector<StyledLine> ofxFontStash2::layoutLines(const vector<StyledText> &blocks, float targetWidth, bool debug ){ float x = 0; float y = 0; if (targetWidth < 0) return vector<StyledLine>(); float xx = x; float yy = y; TS_START_NIF("split words"); vector<SplitTextBlock> words = splitWords(blocks); TS_STOP_NIF("split words"); if (words.size() == 0) return vector<StyledLine>(); vector<StyledLine> lines; // here we create the first line. a few things to note: // - in general, like in a texteditor, the line exists first, then content is added to it. // - 'line' here refers to the visual representation. even a line with no newline (\n) can span multiple lines lines.push_back(StyledLine()); ofxFontStashStyle currentStyle; currentStyle.fontSize = -1; // this makes sure the first style is actually applied, even if it's the default style float lineWidth = 0; int wordsThisLine = 0; vector<float> lineHeigts; float currentLineH = 0; float bounds[4]; float dx; LineElement le; TS_START("walk words"); for(int i = 0; i < words.size(); i++){ StyledLine ¤tLine = lines.back(); //TS_START_ACC("word style"); if(words[i].styledText.style.valid && currentStyle != words[i].styledText.style ){ //cout << " new style!" << endl; currentStyle = words[i].styledText.style; if(applyStyle(currentStyle)){ fonsVertMetrics(fs, NULL, NULL, ¤tLineH); currentLineH/=pixelDensity; }else{ ofLogError() << "no style font defined!"; } } //TS_STOP_ACC("word style"); bool stayOnCurrentLine = true; if( words[i].type == SEPARATOR_INVISIBLE && words[i].styledText.text == "\n" ){ stayOnCurrentLine = false; dx = 0; // add a zero-width enter mark. this is used to keep track // of the vertical spacing of empty lines. le = LineElement(words[i], ofRectangle(xx,yy,0,currentLineH)); le.baseLineY = yy; le.x = xx; le.lineHeight = currentLineH; currentLine.elements.push_back(le); float lineH = calcLineHeight(currentLine); currentLine.lineH = lineH; currentLine.lineW = xx - x + dx; // no! //i--; //re-calc dimensions of this word on a new line! yy += lineH; lineWidth = 0; wordsThisLine = 0; xx = x; lines.push_back(StyledLine()); continue; } else{ //TS_START_ACC("fonsTextBounds"); // applyStyle() already upscaled the font size for the display resolution. // Here we do the same for x/y. // The result gets the inverse treatment. dx = fonsTextBounds( fs, xx*pixelDensity, yy*pixelDensity, words[i].styledText.text.c_str(), NULL, &bounds[0] )/pixelDensity; bounds[0]/=pixelDensity; bounds[1]/=pixelDensity; bounds[2]/=pixelDensity; bounds[3]/=pixelDensity; //TS_STOP_ACC("fonsTextBounds"); //hansi: using dx instead of bounds[2]-bounds[0] //dx is the right size for spacing out text. bounds give exact area of the char, which isn't so useful here. ofRectangle where = ofRectangle(bounds[0], bounds[1] , dx, bounds[3] - bounds[1]); le = LineElement(words[i], where); le.baseLineY = yy; le.x = xx; le.lineHeight = currentLineH; float nextWidth = lineWidth + dx; //if not wider than targetW // || //this is the 1st word in this line but even that doesnt fit stayOnCurrentLine = nextWidth < targetWidth || (wordsThisLine == 0 && (nextWidth >= targetWidth)); } if (stayOnCurrentLine){ //TS_START_ACC("stay on line"); currentLine.elements.push_back(le); lineWidth += dx; xx += dx; wordsThisLine++; //TS_STOP_ACC("stay on line"); } else if( words[i].type == SEPARATOR_INVISIBLE && words[i].styledText.text == " " ){ // ignore spaces when moving into the next line! continue; } else{ //too long, start a new line //TS_START_ACC("new line"); //calc height for this line - taking in account all words in the line float lineH = lineHeightMultiplier * calcLineHeight(currentLine); currentLine.lineH = lineH; currentLine.lineW = xx - x; i--; //re-calc dimensions of this word on a new line! yy += lineH; lineWidth = 0; wordsThisLine = 0; xx = x; lines.push_back(StyledLine()); //TS_STOP_ACC("new line"); } } //TS_START_ACC("last line"); // update dimensions of the last line StyledLine ¤tLine = lines.back(); if( currentLine.elements.size() == 0 ){ // but at least one spacing character, so we have a line height. le = LineElement(SplitTextBlock(SEPARATOR_INVISIBLE,"",currentStyle), ofRectangle(xx,yy,0,currentLineH)); le.baseLineY = yy; le.x = xx; le.lineHeight = currentLineH; currentLine.elements.push_back(le); } float lineH = calcLineHeight(currentLine); currentLine.lineH = lineH; currentLine.lineW = xx - x + dx; //TS_STOP_ACC("last line"); TS_STOP("walk words"); return lines; }