1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details 2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt 3 4 #include "Gui.h" 5 6 #include "graphics/Renderer.h" 7 #include "text/TextSupport.h" 8 #include "utils.h" 9 10 static const float PARAGRAPH_SPACING = 1.5f; 11 12 namespace Gui { 13 TextLayout(const char * _str,RefCountedPtr<Text::TextureFont> font,ColourMarkupMode markup)14 TextLayout::TextLayout(const char *_str, RefCountedPtr<Text::TextureFont> font, ColourMarkupMode markup) 15 { 16 // XXX ColourMarkupSkip not correctly implemented yet 17 assert(markup != ColourMarkupSkip); 18 19 m_colourMarkup = markup; 20 m_font = font ? font : Gui::Screen::GetFont(); 21 22 SetText(_str); 23 24 prevWidth = -1.0f; 25 prevColor = Color::WHITE; 26 } 27 SetText(const char * _str)28 void TextLayout::SetText(const char *_str) 29 { 30 str = std::string(_str); 31 32 m_justify = false; 33 float wordWidth = 0; 34 const char *wordstart = str.c_str(); 35 words.clear(); 36 37 int i = 0; 38 while (str[i]) { 39 wordWidth = 0; 40 wordstart = &str[i]; 41 42 while (str[i] && str[i] != ' ' && str[i] != '\r' && str[i] != '\n') { 43 /* skip color control code things! */ 44 if ((m_colourMarkup != ColourMarkupNone) && (str[i] == '#')) { 45 unsigned int hexcol; 46 if (sscanf(&str[i], "#%3x", &hexcol) == 1) { 47 i += 4; 48 continue; 49 } 50 } 51 52 Uint32 chr; 53 int n = Text::utf8_decode_char(&chr, &str[i]); 54 assert(n); 55 i += n; 56 57 const Text::TextureFont::Glyph &glyph = m_font->GetGlyph(chr); 58 wordWidth += glyph.advX; 59 60 // XXX this should do kerning 61 } 62 63 words.push_back(word_t(wordstart, wordWidth)); 64 65 if (str[i]) { 66 if (str[i] == '\n') words.push_back(word_t(0, 0)); 67 str[i++] = 0; 68 } 69 } 70 71 prevWidth = -1.0f; 72 prevColor = Color::WHITE; 73 } 74 MeasureSize(const float width,float outSize[2]) const75 void TextLayout::MeasureSize(const float width, float outSize[2]) const 76 { 77 float fontScale[2]; 78 Gui::Screen::GetCoords2Pixels(fontScale); 79 _MeasureSizeRaw(width / fontScale[0], outSize); 80 outSize[0] = ceil(outSize[0] * fontScale[0]); 81 outSize[1] = ceil(outSize[1] * fontScale[1]); 82 } 83 Render(const float width,const Color & color) const84 void TextLayout::Render(const float width, const Color &color) const 85 { 86 PROFILE_SCOPED() 87 if (words.empty()) 88 return; 89 90 float fontScale[2]; 91 Gui::Screen::GetCoords2Pixels(fontScale); 92 93 Graphics::Renderer *r = Gui::Screen::GetRenderer(); 94 95 const matrix4x4f &modelMatrix = r->GetTransform(); 96 Graphics::Renderer::MatrixTicket ticket(r); 97 { 98 const float x = modelMatrix[12]; 99 const float y = modelMatrix[13]; 100 matrix4x4f modelView = matrix4x4f::Identity(); 101 modelView.Translate(floor(x / fontScale[0]) * fontScale[0], floor(y / fontScale[1]) * fontScale[1], 0); 102 modelView.Scale(fontScale[0], fontScale[1], 1); 103 r->SetTransform(modelView); 104 m_font->RenderBuffer(m_vbuffer.Get(), color); 105 } 106 } 107 Update(const float width,const Color & color)108 void TextLayout::Update(const float width, const Color &color) 109 { 110 PROFILE_SCOPED() 111 if (words.empty()) { 112 m_vbuffer.Reset(); 113 return; 114 } 115 116 // see if anything has changed 117 if (is_equal_exact(prevWidth, width) && (prevColor == color)) { 118 return; 119 } 120 121 prevWidth = width; 122 prevColor = color; 123 124 float fontScale[2]; 125 Gui::Screen::GetCoords2Pixels(fontScale); 126 127 Graphics::Renderer *r = Gui::Screen::GetRenderer(); 128 Graphics::Renderer::MatrixTicket ticket(r); 129 130 Graphics::VertexArray va(Graphics::ATTRIB_POSITION | Graphics::ATTRIB_DIFFUSE | Graphics::ATTRIB_UV0); 131 132 if (r) { 133 const float maxWidth = width / fontScale[0]; 134 135 float py = 0; 136 137 const float spaceWidth = m_font->GetGlyph(' ').advX; 138 139 Color c = color; 140 141 // vertex array pre-assignment, because TextureFont botches it 142 // over-reserves for markup, but we don't care 143 int numChars = 0; 144 std::list<word_t>::const_iterator wpos = this->words.begin(); 145 for (; wpos != this->words.end(); ++wpos) 146 if ((*wpos).word) numChars += strlen((*wpos).word); 147 148 va.position.reserve(6 * numChars); 149 va.diffuse.reserve(6 * numChars); 150 va.uv0.reserve(6 * numChars); 151 152 // build lines of text 153 wpos = this->words.begin(); 154 while (wpos != this->words.end()) { 155 float len = 0; 156 int num = 0; 157 158 std::list<word_t>::const_iterator i = wpos; 159 len += (*i).advx; 160 num++; 161 bool overflow = false; 162 bool explicit_newline = false; 163 if ((*i).word != 0) { 164 ++i; 165 for (; i != this->words.end(); ++i) { 166 if ((*i).word == 0) { 167 // newline 168 explicit_newline = true; 169 num++; 170 break; 171 } 172 if (len + spaceWidth + (*i).advx > maxWidth) { 173 overflow = true; 174 break; 175 } 176 len += (*i).advx + spaceWidth; 177 num++; 178 } 179 } 180 181 float _spaceWidth; 182 if ((m_justify) && (num > 1) && overflow) { 183 float spaceleft = maxWidth - len; 184 _spaceWidth = spaceWidth + (spaceleft / float(num - 1)); 185 } else { 186 _spaceWidth = spaceWidth; 187 } 188 189 float px = 0; 190 for (int j = 0; j < num; j++) { 191 if ((*wpos).word) { 192 const std::string word((*wpos).word); 193 if (m_colourMarkup == ColourMarkupUse) { 194 Color newColor = m_font->PopulateMarkup(va, word, round(px), round(py), c); 195 if (!word.empty()) 196 c = std::move(newColor); 197 } else 198 m_font->PopulateString(va, word, round(px), round(py), c); 199 } 200 px += (*wpos).advx + _spaceWidth; 201 ++wpos; 202 } 203 py += m_font->GetHeight() * (explicit_newline ? PARAGRAPH_SPACING : 1.0f); 204 } 205 } 206 207 if (va.GetNumVerts() > 0) { 208 if (!m_vbuffer.Valid() || m_vbuffer->GetCapacity() < va.GetNumVerts()) { 209 //create buffer and upload data 210 Graphics::VertexBufferDesc vbd; 211 vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION; 212 vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3; 213 vbd.attrib[1].semantic = Graphics::ATTRIB_DIFFUSE; 214 vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_UBYTE4; 215 vbd.attrib[2].semantic = Graphics::ATTRIB_UV0; 216 vbd.attrib[2].format = Graphics::ATTRIB_FORMAT_FLOAT2; 217 vbd.numVertices = va.GetNumVerts(); 218 vbd.usage = Graphics::BUFFER_USAGE_DYNAMIC; // we could be updating this per-frame 219 m_vbuffer.Reset(r->CreateVertexBuffer(vbd)); 220 } 221 222 m_vbuffer->Populate(va); 223 } else { 224 m_vbuffer.Reset(); 225 } 226 } 227 _MeasureSizeRaw(const float layoutWidth,float outSize[2]) const228 void TextLayout::_MeasureSizeRaw(const float layoutWidth, float outSize[2]) const 229 { 230 outSize[0] = 0; 231 outSize[1] = 0; 232 233 const float spaceWidth = m_font->GetGlyph(' ').advX; 234 235 // build lines of text 236 for (std::list<word_t>::const_iterator wpos = words.begin(); wpos != words.end();) { 237 float len = 0; 238 int num = 0; 239 bool explicit_newline = false; 240 241 std::list<word_t>::const_iterator i = wpos; 242 len += (*i).advx; 243 num++; 244 bool overflow = false; 245 if ((*i).word != 0) { 246 ++i; 247 for (; i != words.end(); ++i) { 248 if ((*i).word == 0) { 249 // newline 250 explicit_newline = true; 251 num++; 252 break; 253 } 254 if (len + spaceWidth + (*i).advx > layoutWidth) { 255 overflow = true; 256 break; 257 } 258 len += (*i).advx + spaceWidth; 259 num++; 260 } 261 } 262 263 float _spaceWidth; 264 if ((m_justify) && (num > 1) && overflow) { 265 float spaceleft = layoutWidth - len; 266 _spaceWidth = spaceWidth + (spaceleft / float(num - 1)); 267 } else { 268 _spaceWidth = spaceWidth; 269 } 270 271 float lineLen = 0; 272 for (int j = 0; j < num; j++) { 273 word_t word = (*wpos); 274 lineLen += word.advx; 275 if (j < num - 1) lineLen += _spaceWidth; 276 ++wpos; 277 } 278 if (lineLen > outSize[0]) outSize[0] = lineLen; 279 outSize[1] += m_font->GetHeight() * (explicit_newline ? PARAGRAPH_SPACING : 1.0f); 280 } 281 if (outSize[1] > 0.0f) 282 outSize[1] += m_font->GetDescender(); 283 } 284 285 } // namespace Gui 286