float CglFont::GetTextWidth_(const std::u8string& text) { if (text.empty()) return 0.0f; float w = 0.0f; float maxw = 0.0f; const GlyphInfo* prv_g=NULL; const GlyphInfo* cur_g; int pos = 0; while (pos < text.length()) { const char32_t u = Utf8GetNextChar(text, pos); switch (u) { // inlined colorcode case ColorCodeIndicator: pos = SkipColorCodes(text, pos - 1); if (pos<0) { pos = text.length(); } break; // reset color case ColorResetIndicator: break; // newline case 0x0d: // CR+LF if (pos < text.length() && text[pos] == 0x0a) pos++; case 0x0a: // LF if (prv_g) w += prv_g->advance; if (w > maxw) maxw = w; w = 0.0f; prv_g = NULL; break; // printable char default: cur_g = &GetGlyph(u); if (prv_g) w += GetKerning(*prv_g, *cur_g); prv_g = cur_g; } } if (prv_g) w += prv_g->advance; if (w > maxw) maxw = w; return maxw; }
void CglFont::RenderStringOutlined(float x, float y, const float& scaleX, const float& scaleY, const std::string& str) { const float shiftX = (scaleX/fontSize) * GetOutlineWidth(), shiftY = (scaleY/fontSize) * GetOutlineWidth(); const float startx = x; const float lineHeight_ = scaleY * GetLineHeight(); const std::u8string& ustr = toustring(str); const size_t length = str.length(); va.EnlargeArrays(length * 4, 0, VA_SIZE_2DT); va2.EnlargeArrays(length * 4, 0, VA_SIZE_2DT); int skippedLines; bool colorChanged; const GlyphInfo* g = NULL; float4 newColor = textColor; char32_t c; int i = 0; do { const bool endOfString = SkipColorCodesAndNewLines(ustr, &i, &newColor, &colorChanged, &skippedLines, &baseTextColor); if (endOfString) return; c = Utf8GetNextChar(str,i); if (colorChanged) { if (autoOutlineColor) { SetColors(&newColor,NULL); } else { SetTextColor(&newColor); } } const GlyphInfo* c_g = &GetGlyph(c); if (skippedLines>0) { x = startx; y -= skippedLines * lineHeight_; } else if (g) { x += scaleX * GetKerning(*g, *c_g); } g = c_g; const auto& tc = g->texCord; const auto& stc = g->shadowTexCord; const float dx0 = (scaleX * g->size.x0()) + x, dy0 = (scaleY * g->size.y0()) + y; const float dx1 = (scaleX * g->size.x1()) + x, dy1 = (scaleY * g->size.y1()) + y; // draw outline va2.AddVertexQ2dT(dx0-shiftX, dy1-shiftY, stc.x0(), stc.y1()); va2.AddVertexQ2dT(dx0-shiftX, dy0+shiftY, stc.x0(), stc.y0()); va2.AddVertexQ2dT(dx1+shiftX, dy0+shiftY, stc.x1(), stc.y0()); va2.AddVertexQ2dT(dx1+shiftX, dy1-shiftY, stc.x1(), stc.y1()); // draw the actual character va.AddVertexQ2dT(dx0, dy1, tc.x0(), tc.y1()); va.AddVertexQ2dT(dx0, dy0, tc.x0(), tc.y0()); va.AddVertexQ2dT(dx1, dy0, tc.x1(), tc.y0()); va.AddVertexQ2dT(dx1, dy1, tc.x1(), tc.y1()); } while(true); }
void CglFont::RenderString(float x, float y, const float& scaleX, const float& scaleY, const std::string& str) { /** * NOTE: * Font rendering does not use display lists, but VAs. It's actually faster * (450% faster with a 7600GT!) for these reasons: * * 1. When using DLs, we can not group multiple glyphs into one glBegin/End pair * because glTranslatef can not go between such a pair. * 2. We can now eliminate all glPushMatrix/PopMatrix pairs related to font rendering * because the transformations are calculated on the fly. These are just a couple of * floating point multiplications and shouldn't be too expensive. */ const float startx = x; const float lineHeight_ = scaleY * GetLineHeight(); unsigned int length = (unsigned int)str.length(); const std::u8string& ustr = toustring(str); va.EnlargeArrays(length * 4, 0, VA_SIZE_2DT); int skippedLines; bool colorChanged; const GlyphInfo* g = NULL; float4 newColor = textColor; char32_t c; int i = 0; do { const bool endOfString = SkipColorCodesAndNewLines(ustr, &i, &newColor, &colorChanged, &skippedLines, &baseTextColor); if (endOfString) return; c = Utf8GetNextChar(str,i); if (colorChanged) { if (autoOutlineColor) { SetColors(&newColor,NULL); } else { SetTextColor(&newColor); } } const GlyphInfo* c_g = &GetGlyph(c); if (skippedLines>0) { x = startx; y -= skippedLines * lineHeight_; } else if (g) { x += scaleX * GetKerning(*g, *c_g); } g = c_g; const auto& tc = g->texCord; const float dx0 = (scaleX * g->size.x0()) + x, dy0 = (scaleY * g->size.y0()) + y; const float dx1 = (scaleX * g->size.x1()) + x, dy1 = (scaleY * g->size.y1()) + y; va.AddVertexQ2dT(dx0, dy1, tc.x0(), tc.y1()); va.AddVertexQ2dT(dx0, dy0, tc.x0(), tc.y0()); va.AddVertexQ2dT(dx1, dy0, tc.x1(), tc.y0()); va.AddVertexQ2dT(dx1, dy1, tc.x1(), tc.y1()); } while(true); }
float CFont::AddStr(CStrAny &sStr, const CVector<2> &vPos, char chPrevious) { ASSERT(IsValid()); if (!sStr.Length()) return 0; UINT uiVerts, uiInds, uiVertSize; uiVerts = sStr.Length() * 4; uiInds = sStr.Length() * 6; CGeometry *pGeom = m_pTextModel->m_pGeometry; uiVertSize = pGeom->m_pInputDesc->GetSize(); ASSERT(uiVertSize == sizeof(TPlainVertex)); if (pGeom->m_pVB->GetSize(0) < (pGeom->m_uiVertices + uiVerts) * uiVertSize) return INVALID_LENGTH; if (pGeom->m_pIB->GetSize(0) < (pGeom->m_uiIndices + uiInds) * sizeof(uint16_t)) return INVALID_LENGTH; TPlainVertex *pVerts; uint16_t *pInds; pVerts = (TPlainVertex *) pGeom->m_pVB->Map(0, CResource::RMF_SYSTEM_ONLY, pGeom->m_uiVertices * uiVertSize, uiVerts * uiVertSize); ASSERT(pVerts); pInds = (uint16_t *) pGeom->m_pIB->Map(0, CResource::RMF_SYSTEM_ONLY, pGeom->m_uiIndices * sizeof(uint16_t), uiInds * sizeof(uint16_t)); ASSERT(pInds); CVector<3> vCur = { vPos.x(), vPos.y() - m_iAscent, 0.5f }, vBoxSize = { 0, (float) m_iHeight, 0 }; int i, iChars = 0; for (i = 0; i < sStr.Length(); i++) { char ch = sStr[i]; if (!m_Chars[(uint8_t) ch].ch) continue; CRect<> rcInTex = GetCharRect(ch); vBoxSize.x() = (float) m_Chars[(uint8_t) ch].iB; vCur.x() += m_Chars[(uint8_t) ch].iA + GetKerning(chPrevious, ch); pVerts[0].vPos.Set(vCur.x(), vCur.y() + vBoxSize.y(), vCur.z()); pVerts[0].vTex = rcInTex.m_vMin; pVerts[1].vPos = vCur; pVerts[1].vTex = rcInTex.GetPoint(0, 1); pVerts[2].vPos = vCur + vBoxSize; pVerts[2].vTex = rcInTex.GetPoint(1, 0); pVerts[3].vPos.Set(vCur.x() + vBoxSize.x(), vCur.y(), vCur.z()); pVerts[3].vTex = rcInTex.m_vMax; pInds[0] = pGeom->m_uiVertices + iChars * 4; pInds[1] = pInds[0] + 1; pInds[2] = pInds[0] + 2; pInds[3] = pInds[0] + 2; pInds[4] = pInds[0] + 1; pInds[5] = pInds[0] + 3; vCur.x() += m_Chars[(uint8_t) ch].iB + m_Chars[(uint8_t) ch].iC; chPrevious = ch; pVerts += 4; pInds += 6; iChars++; } pGeom->m_pIB->Unmap(); pGeom->m_pVB->Unmap(); pGeom->m_uiVertices += iChars * 4; pGeom->m_uiIndices += iChars * 6; return vCur.x() - vPos.x(); }
CBSurface *CBFontTT::RenderTextToTexture(const WideString &text, int width, TTextAlign align, int maxHeight, int &textOffset) { TextLineList lines; WrapText(text, width, maxHeight, lines); TextLineList::iterator it; int textHeight = lines.size() * (m_MaxCharHeight + m_Ascender); SDL_Surface *surface = SDL_CreateRGBSurface(0, width, textHeight, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); SDL_LockSurface(surface); int posY = (int)GetLineHeight() - (int)m_Descender; for (it = lines.begin(); it != lines.end(); ++it) { TextLine *line = (*it); int posX = 0; switch (align) { case TAL_CENTER: posX += (width - line->GetWidth()) / 2; break; case TAL_RIGHT: posX += width - line->GetWidth(); break; } textOffset = 0; for (size_t i = 0; i < line->GetText().length(); i++) { wchar_t ch = line->GetText()[i]; GlyphInfo *glyph = m_GlyphCache->GetGlyph(ch); if (!glyph) continue; textOffset = std::max(textOffset, glyph->GetBearingY()); } int origPosX = posX; wchar_t prevChar = L'\0'; for (size_t i = 0; i < line->GetText().length(); i++) { wchar_t ch = line->GetText()[i]; GlyphInfo *glyph = m_GlyphCache->GetGlyph(ch); if (!glyph) continue; float kerning = 0; if (prevChar != L'\0') kerning = GetKerning(prevChar, ch); posX += (int)kerning; if (glyph->GetBearingY() > 0) { int i = 10; } SDL_Rect rect; rect.x = posX + glyph->GetBearingX(); rect.y = posY - glyph->GetBearingY() + textOffset; rect.w = glyph->GetImage()->w; rect.h = glyph->GetImage()->h; BlitSurface(glyph->GetImage(), surface, &rect); prevChar = ch; posX += (int)(glyph->GetAdvanceX()); posY += (int)(glyph->GetAdvanceY()); } if (m_IsUnderline) { for (int i = origPosX; i < origPosX + line->GetWidth(); i++) { Uint8 *buf = (Uint8 *)surface->pixels + (int)(m_UnderlinePos + m_Ascender) * surface->pitch; Uint32 *buf32 = (Uint32 *)buf; buf32[i] = SDL_MapRGBA(surface->format, 255, 255, 255, 255); } } SDL_UnlockSurface(surface); delete line; line = NULL; posY += GetLineHeight(); } CBSurfaceSDL *wmeSurface = new CBSurfaceSDL(Game); if (SUCCEEDED(wmeSurface->CreateFromSDLSurface(surface))) { SDL_FreeSurface(surface); return wmeSurface; } else { SDL_FreeSurface(surface); delete wmeSurface; return NULL; } }
// Generates the geometry required to render a single line of text. int FontFaceHandle::GenerateString(GeometryList& geometry, const WString& string, const Vector2f& position, const Colourb& colour, int layer_configuration_index, word default_character) const { int geometry_index = 0; int line_width = 0; ROCKET_ASSERT(layer_configuration_index >= 0); ROCKET_ASSERT(layer_configuration_index < (int) layer_configurations.size()); // Fetch the requested configuration and generate the geometry for each one. const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index]; for (size_t i = 0; i < layer_configuration.size(); ++i) { FontFaceLayer* layer = layer_configuration[i]; Colourb layer_colour; if (layer == base_layer) layer_colour = colour; else layer_colour = layer->GetColour(); // Resize the geometry list if required. if ((int) geometry.size() < geometry_index + layer->GetNumTextures()) geometry.resize(geometry_index + layer->GetNumTextures()); // Bind the textures to the geometries. for (int i = 0; i < layer->GetNumTextures(); ++i) geometry[geometry_index + i].SetTexture(layer->GetTexture(i)); line_width = 0; word prior_character = 0; const word* string_iterator = string.CString(); const word* string_end = string.CString() + string.Length(); word final_character; for (; string_iterator != string_end; string_iterator++) { final_character = *string_iterator; FontGlyphMap::const_iterator iterator = glyphs.find(*string_iterator); if (iterator == glyphs.end()) { if (default_character >= 32) { iterator = glyphs.find(default_character); if (iterator == glyphs.end()) { continue; } else { final_character = default_character; } } else { continue; } } // Adjust the cursor for the kerning between this character and the previous one. if (prior_character != 0) line_width += GetKerning(prior_character, final_character); layer->GenerateGeometry(&geometry[geometry_index], final_character, Vector2f(position.x + line_width, position.y), layer_colour); line_width += iterator->second.advance; prior_character = final_character; } geometry_index += layer->GetNumTextures(); } // Cull any excess geometry from a previous generation. geometry.resize(geometry_index); return line_width; }
CTextWrap::word CTextWrap::SplitWord(CTextWrap::word& w, float wantedWidth, bool smart) { // returns two pieces 'L'eft and 'R'ight of the split word (returns L, *wi becomes R) word w2; w2.pos = w.pos; const float spaceAdvance = GetGlyph(spaceUTF16).advance; if (w.isLineBreak) { // shouldn't happen w2 = w; w.isSpace = true; } else if (w.isSpace) { const int split = (int)std::floor(wantedWidth / spaceAdvance); w2.isSpace = true; w2.numSpaces = split; w2.width = spaceAdvance * w2.numSpaces; w.numSpaces -= split; w.width = spaceAdvance * w.numSpaces; w.pos += split; } else { if (smart) { if ( (wantedWidth < 8 * spaceAdvance) || (w.text.length() < 1) ) { w2.isSpace = true; return w2; } } float width = 0.0f; int i = 0; float min_penalty = 1e9; unsigned int goodbreak = 0; char32_t c = Utf8GetNextChar(w.text,i); const GlyphInfo* curGlyph = &GetGlyph(c); const GlyphInfo* nextGlyph = curGlyph; do { const int lastCharPos = i; const char32_t co = c; curGlyph = nextGlyph; c = Utf8GetNextChar(w.text,i); nextGlyph = &GetGlyph(c); width += GetKerning(*curGlyph, *nextGlyph); if (width > wantedWidth) { break; } if (smart) { const float penalty = GetPenalty(co, lastCharPos, w.text.length()); if (penalty < min_penalty) { min_penalty = penalty; goodbreak = lastCharPos; } } else { goodbreak = i; } } while(i < w.text.length()); w2.text = toustring(w.text.substr(0,goodbreak)); w2.width = GetTextWidth(w2.text); w.text.erase(0,goodbreak); w.width = GetTextWidth(w.text); w.pos += goodbreak; } return w2; }