void TTF_TextBox(const drawTextCommand_t *cmd, const char *string, int count) { TTFtextRow rows[2]; int nrows = 0, i; int oldAlign = state.align; int halign = state.align & (FONS_ALIGN_LEFT | FONS_ALIGN_CENTER | FONS_ALIGN_RIGHT); int valign = state.align & (FONS_ALIGN_TOP | FONS_ALIGN_MIDDLE | FONS_ALIGN_BOTTOM | FONS_ALIGN_BASELINE); float lineh; fonsPushState(ctx); fonsVertMetrics(ctx, nullptr, nullptr, &lineh); fonsSetAlign(ctx, FONS_ALIGN_LEFT | valign); float x = cmd->x; float y = cmd->y; lineh *= state.lineHeight; const char *end = TTF_CountChars(string, count); while ((nrows = TTF_BreakLines(string, end, cmd->w, rows, 2)) > 0) { for (i = 0; i < nrows; i++) { TTFtextRow* row = &rows[i]; if (halign & FONS_ALIGN_LEFT) fonsDrawText(ctx, x, y, row->start, row->end); else if (halign & FONS_ALIGN_CENTER) fonsDrawText(ctx, x + cmd->w * 0.5f - row->width*0.5f, y, row->start, row->end); else if (halign & FONS_ALIGN_RIGHT) fonsDrawText(ctx, x + cmd->w - row->width, y, row->start, row->end); y += lineh; } string = rows[nrows - 1].next; } fonsPopState(ctx); }
const char * TTF_BreakString(int w, const char *in) { TTFtextRow rows[2]; int nrows = 0, i; int oldAlign = state.align; int halign = state.align & (FONS_ALIGN_LEFT | FONS_ALIGN_CENTER | FONS_ALIGN_RIGHT); int valign = state.align & (FONS_ALIGN_TOP | FONS_ALIGN_MIDDLE | FONS_ALIGN_BOTTOM | FONS_ALIGN_BASELINE); float lineh; fonsVertMetrics(ctx, nullptr, nullptr, &lineh); fonsSetAlign(ctx, FONS_ALIGN_LEFT | valign); if (splitStr != nullptr) { sdsfree(splitStr); } splitStr = sdsempty(); while ((nrows = TTF_BreakLines(in, nullptr, w, rows, 2)) > 0) { for (i = 0; i < nrows; i++) { TTFtextRow* row = &rows[i]; splitStr = sdscatlen(splitStr, row->start, row->end - row->start); splitStr = sdscat(splitStr, "\n"); } in = rows[nrows - 1].next; } fonsSetAlign(ctx, oldAlign); return splitStr; }
void ofxFontStash2::getVerticalMetrics(const ofxFontStashStyle & style, float* ascender, float* descender, float* lineH){ applyStyle(style); fonsVertMetrics(fs, ascender, descender, lineH); if( pixelDensity != 1 ){ *ascender /= pixelDensity; *descender /= pixelDensity; *lineH /= pixelDensity; } }
FontContext::FontMetrics FontContext::getMetrics() { FontMetrics metrics; fonsVertMetrics(m_fsContext, &metrics.ascender, &metrics.descender, &metrics.lineHeight); return metrics; }
int main() { int fontNormal = FONS_INVALID; int fontItalic = FONS_INVALID; int fontBold = FONS_INVALID; int fontJapanese = FONS_INVALID; GLFWwindow* window; const GLFWvidmode* mode; struct FONSparams params; struct FONScontext* fs = NULL; if (!glfwInit()) return -1; mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); window = glfwCreateWindow(mode->width - 40, mode->height - 80, "Font Stash", NULL, NULL); if (!window) { glfwTerminate(); return -1; } glfwMakeContextCurrent(window); memset(¶ms, 0, sizeof(params)); params.width = 512; params.height = 512; params.flags = FONS_ZERO_TOPLEFT; if (glstInit(¶ms) == 0) { printf("Could not create renderer.\n"); return -1; } fs = fonsCreate(¶ms); if (fs == NULL) { printf("Could not create stash.\n"); return -1; } fontNormal = fonsAddFont(fs, "../example/DroidSerif-Regular.ttf"); if (fontNormal == FONS_INVALID) { printf("Could not add font normal.\n"); return -1; } fontItalic = fonsAddFont(fs, "../example/DroidSerif-Italic.ttf"); if (fontItalic == FONS_INVALID) { printf("Could not add font italic.\n"); return -1; } fontBold = fonsAddFont(fs, "../example/DroidSerif-Bold.ttf"); if (fontBold == FONS_INVALID) { printf("Could not add font bold.\n"); return -1; } fontJapanese = fonsAddFont(fs, "../example/DroidSansJapanese.ttf"); if (fontJapanese == FONS_INVALID) { printf("Could not add font japanese.\n"); return -1; } while (!glfwWindowShouldClose(window)) { float sx, sy, dx, dy, lh = 0; int width, height; glfwGetFramebufferSize(window, &width, &height); // Update and render glViewport(0, 0, width, height); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0,width,height,0,-1,1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glColor4ub(255,255,255,255); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); unsigned int white = glstRGBA(255,255,255,255); unsigned int brown = glstRGBA(192,128,0,128); unsigned int blue = glstRGBA(0,192,255,255); sx = 100; sy = 100; dx = sx; dy = sy; dash(dx,dy); fonsSetSize(fs, 124.0f); fonsSetFont(fs, fontNormal); fonsVertMetrics(fs, NULL, NULL, &lh); dx = sx; dy += lh; dash(dx,dy); fonsSetSize(fs, 124.0f); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); fonsDrawText(fs, dx,dy,"The quick ",&dx); fonsSetSize(fs, 48.0f); fonsSetFont(fs, fontItalic); fonsSetColor(fs, brown); fonsDrawText(fs, dx,dy,"brown ",&dx); fonsSetSize(fs, 24.0f); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); fonsDrawText(fs, dx,dy,"fox ",&dx); fonsVertMetrics(fs, NULL, NULL, &lh); dx = sx; dy += lh*1.2f; dash(dx,dy); fonsSetFont(fs, fontItalic); fonsDrawText(fs, dx,dy,"jumps over ",&dx); fonsSetFont(fs, fontBold); fonsDrawText(fs, dx,dy,"the lazy ",&dx); fonsSetFont(fs, fontNormal); fonsDrawText(fs, dx,dy,"dog.",&dx); dx = sx; dy += lh*1.2f; dash(dx,dy); fonsSetSize(fs, 12.0f); fonsSetFont(fs, fontNormal); fonsSetColor(fs, blue); fonsDrawText(fs, dx,dy,"Now is the time for all good men to come to the aid of the party.",&dx); fonsVertMetrics(fs, NULL,NULL,&lh); dx = sx; dy += lh*1.2f*2; dash(dx,dy); fonsSetSize(fs, 18.0f); fonsSetFont(fs, fontItalic); fonsSetColor(fs, white); fonsDrawText(fs, dx,dy,"Ég get etið gler án þess að meiða mig.",&dx); fonsVertMetrics(fs, NULL,NULL,&lh); dx = sx; dy += lh*1.2f; dash(dx,dy); fonsSetFont(fs, fontJapanese); fonsDrawText(fs, dx,dy,"私はガラスを食べられます。それは私を傷つけません。asd",&dx); glEnable(GL_DEPTH_TEST); glfwSwapBuffers(window); glfwPollEvents(); } fonsDelete(fs); glfwTerminate(); return 0; }
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(); } }
int main() { int fontNormal = FONS_INVALID; int fontItalic = FONS_INVALID; int fontBold = FONS_INVALID; int fontJapanese = FONS_INVALID; GLFWwindow* window; const GLFWvidmode* mode; if (!glfwInit()) return -1; mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); window = glfwCreateWindow(mode->width - 40, mode->height - 80, "Font Stash", NULL, NULL); if (!window) { glfwTerminate(); return -1; } glfwSetKeyCallback(window, key); glfwMakeContextCurrent(window); fs = glfonsCreate(256, 256, FONS_ZERO_TOPLEFT); if (fs == NULL) { printf("Could not create stash.\n"); return -1; } fonsSetErrorCallback(fs, stashError, fs); fontNormal = fonsAddFont(fs, "sans", "../example/DroidSerif-Regular.ttf"); if (fontNormal == FONS_INVALID) { printf("Could not add font normal.\n"); return -1; } fontItalic = fonsAddFont(fs, "sans-italic", "../example/DroidSerif-Italic.ttf"); if (fontItalic == FONS_INVALID) { printf("Could not add font italic.\n"); return -1; } fontBold = fonsAddFont(fs, "sans-bold", "../example/DroidSerif-Bold.ttf"); if (fontBold == FONS_INVALID) { printf("Could not add font bold.\n"); return -1; } fontJapanese = fonsAddFont(fs, "sans-jp", "../example/DroidSansJapanese.ttf"); if (fontJapanese == FONS_INVALID) { printf("Could not add font japanese.\n"); return -1; } while (!glfwWindowShouldClose(window)) { float sx, sy, dx, dy, lh = 0; int width, height; int atlasw, atlash; unsigned int white,black,brown,blue; char msg[64]; glfwGetFramebufferSize(window, &width, &height); // Update and render glViewport(0, 0, width, height); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0,width,height,0,-1,1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glColor4ub(255,255,255,255); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); white = glfonsRGBA(255,255,255,255); brown = glfonsRGBA(192,128,0,128); blue = glfonsRGBA(0,192,255,255); black = glfonsRGBA(0,0,0,255); sx = 50; sy = 50; dx = sx; dy = sy; dash(dx,dy); fonsClearState(fs); fonsSetSize(fs, size); fonsSetFont(fs, fontNormal); fonsVertMetrics(fs, NULL, NULL, &lh); dx = sx; dy += lh; dash(dx,dy); fonsSetSize(fs, size); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); dx = fonsDrawText(fs, dx,dy,"The quick ",NULL); fonsSetSize(fs, size/2); fonsSetFont(fs, fontItalic); fonsSetColor(fs, brown); dx = fonsDrawText(fs, dx,dy,"brown ",NULL); fonsSetSize(fs, size/3); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); dx = fonsDrawText(fs, dx,dy,"fox ",NULL); fonsSetSize(fs, 14); fonsSetFont(fs, fontNormal); fonsSetColor(fs, white); fonsDrawText(fs, 20, height-20,"Press UP / DOWN keys to change font size and to trigger atlas full callback, R to reset atlas, E to expand atlas.",NULL); fonsGetAtlasSize(fs, &atlasw, &atlash); snprintf(msg, sizeof(msg), "Atlas: %d × %d", atlasw, atlash); fonsDrawText(fs, 20, height-50, msg, NULL); fonsDrawDebug(fs, width - atlasw - 20, 20.0); glEnable(GL_DEPTH_TEST); glfwSwapBuffers(window); glfwPollEvents(); } glfonsDelete(fs); glfwTerminate(); return 0; }
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; }