1 /* GG is a GUI for OpenGL.
2 Copyright (C) 2003-2008 T. Zachary Laine
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public License
6 as published by the Free Software Foundation; either version 2.1
7 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free
16 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17 02111-1307 USA
18
19 If you do not wish to comply with the terms of the LGPL please
20 contact the author as other terms are available for a fee.
21
22 Zach Laine
23 whatwasthataddress@gmail.com */
24
25 #include <GG/Font.h>
26
27 #include <GG/GUI.h>
28 #include <GG/Base.h>
29 #include <GG/StyleFactory.h>
30 #include <GG/utf8/checked.h>
31 #include <GG/GLClientAndServerBuffer.h>
32
33 #include <ft2build.h>
34 #include FT_FREETYPE_H
35
36 #include <boost/format.hpp>
37 #include <boost/lexical_cast.hpp>
38 #include <boost/xpressive/xpressive.hpp>
39 #include <boost/xpressive/regex_actions.hpp>
40
41 #include <cctype>
42 #include <cmath>
43 #include <iterator>
44 #include <numeric>
45 #include <sstream>
46 #include <unordered_set>
47
48 #define DEBUG_DETERMINELINES 0
49
50 namespace GG { namespace detail {
51
FTFaceWrapper()52 FTFaceWrapper::FTFaceWrapper()
53 {}
54
~FTFaceWrapper()55 FTFaceWrapper::~FTFaceWrapper()
56 { if (m_face) FT_Done_Face(m_face); }
57
58 } }
59
60 using namespace GG;
61
62 namespace {
63 const std::uint32_t WIDE_SPACE = ' ';
64 const std::uint32_t WIDE_NEWLINE = '\n';
65 const std::uint32_t WIDE_CR = '\r';
66 const std::uint32_t WIDE_FF = '\f';
67 const std::uint32_t WIDE_TAB = '\t';
68
69 const std::string ITALIC_TAG = "i";
70 const std::string SHADOW_TAG = "s";
71 const std::string UNDERLINE_TAG = "u";
72 const std::string SUPERSCRIPT_TAG = "sup";
73 const std::string SUBSCRIPT_TAG = "sub";
74 const std::string RGBA_TAG = "rgba";
75 const std::string ALIGN_LEFT_TAG = "left";
76 const std::string ALIGN_CENTER_TAG = "center";
77 const std::string ALIGN_RIGHT_TAG = "right";
78 const std::string PRE_TAG = "pre";
79
80 template <typename T>
NextPowerOfTwo(T input)81 T NextPowerOfTwo(T input)
82 {
83 T value(1);
84 while (value < input)
85 value *= 2;
86 return value;
87 }
88
89 /** This is used to collect data on the glyphs as they are recorded into buffers, for use in creating Glyph objects at the end
90 of Font's constructor.*/
91 struct TempGlyphData
92 {
TempGlyphData__anon714a98a00111::TempGlyphData93 TempGlyphData() {}
TempGlyphData__anon714a98a00111::TempGlyphData94 TempGlyphData(const Pt& ul_, const Pt& lr_, Y y_ofs, X lb, X a) :
95 ul(ul_), lr(lr_), y_offset(y_ofs), left_b(lb), adv(a) {}
96 Pt ul, lr; ///< area of glyph subtexture within texture
97 Y y_offset; ///< vertical offset to draw texture (may be negative!)
98 X left_b; ///< left bearing (see Glyph)
99 X adv; ///< advance of glyph (see Glyph)
100 };
101
102 /// A two dimensional grid of pixels that expands to
103 /// fit any write much like an stl vector, but in 2d.
104 template<typename T>
105 class Buffer2d
106 {
107 public:
108 /// Create a new 2D buffer
109 /// \param initial_width Initial width to allocate
110 /// \param initial_height Initial height to allocate
111 /// \param default_value The value to fill empty space with whenever it appears
Buffer2d(X initial_width,Y initial_height,const T & default_value)112 Buffer2d(X initial_width, Y initial_height, const T& default_value):
113 m_capacity_width(initial_width),
114 m_capacity_height(initial_height),
115 m_data(Value(initial_width)*Value(initial_height), default_value),
116 m_current_width(initial_width),
117 m_current_height(initial_height),
118 m_default_value(default_value)
119 {}
120
121 /// Access point \x,\y, expanding the buffer if it does not exist yet
at(X x,Y y)122 T& at(X x, Y y)
123 {
124 EnsureFit(x, y);
125 return this->get(x,y);
126 }
127
128 /// Access point \x, \y without any checks
get(X x,Y y)129 T& get(X x, Y y)
130 { return m_data[Value(m_capacity_width)*Value(y) + Value(x)]; }
131
132 /// Returns the current highest x the user has requested to exist
CurrentWidth() const133 X CurrentWidth() const
134 { return m_current_width; }
135
136 /// Returns the current highest y the user has requested to exist
CurrentHeight() const137 Y CurrentHeight() const
138 { return m_capacity_height; }
139
140 /// Returns the actual width of the storage area allocated so far
BufferWidth() const141 X BufferWidth() const
142 { return m_capacity_width; }
143
144 /// Returns the actual height of the storage area allocated so far
BufferHeight() const145 Y BufferHeight() const
146 { return m_capacity_height; }
147
148 /// Return a pointer to the storage buffer where the data is kept
Buffer()149 T* Buffer()
150 { return &*m_data.begin(); }
151
152 /// Returns the size of the storage buffer where the data is kept
BufferSize() const153 std::size_t BufferSize() const
154 { return m_data.size(); }
155
156 /// Makes the size of the underlying buffer the smallest power of power of two
157 /// rectangle that can accommodate CurrentWidth() and CurrentHeight()
MakePowerOfTwo()158 void MakePowerOfTwo()
159 { ResizeCapacity(NextPowerOfTwo(m_current_width), NextPowerOfTwo(m_current_height)); }
160
161 private:
162 X m_capacity_width; // How wide the reserved buffer is
163 Y m_capacity_height; // How hight the reserved buffer is
164 std::vector<T> m_data;
165 X m_current_width; // The highest x coordinate written to
166 Y m_current_height; // The highest y coordinate written to
167 const T m_default_value; // The value with which to fill all emerging empty slots in the buffer
168
EnsureFit(X x,Y y)169 void EnsureFit(X x, Y y)
170 {
171 X new_width = std::max(m_current_width, x + 1); // Zero indexed => width = max_x + 1
172 Y new_height = std::max(m_current_height, y + 1); // Also zero indexed
173 X new_capacity_width = m_capacity_width;
174 Y new_capacity_height = m_capacity_height;
175 while (new_width > new_capacity_width) {
176 new_capacity_width *= 2;
177 }
178 while (new_height > new_capacity_height) {
179 new_capacity_height *= 2;
180 }
181
182 ResizeCapacity(new_capacity_width, new_capacity_height);
183 m_current_width = new_width;
184 m_current_height = new_height;
185 }
186
ResizeCapacity(X new_capacity_width,Y new_capacity_height)187 void ResizeCapacity(X new_capacity_width, Y new_capacity_height)
188 {
189 // If there really was a change, we need to recreate our storage.
190 // This is expensive, but since we double the size every time,
191 // the cost of adding data here in some sane order is amortized constant
192 if (new_capacity_width != m_capacity_width || new_capacity_height != m_capacity_height) {
193 // Create new storage and copy old data. A linear copy won't do, there
194 // will be a new mapping from 2d indexes to 1d memory.
195 std::vector<T> new_data(Value(new_capacity_width)*Value(new_capacity_height), m_default_value);
196 for (Y y_i = Y0; y_i < m_current_height && y_i < new_capacity_height; ++y_i) {
197 for (X x_i = X0; x_i < m_current_width && x_i < new_capacity_width; ++x_i) {
198 unsigned pos = Value(new_capacity_width) * Value(y_i) + Value(x_i);
199 new_data[pos] = get(x_i, y_i);
200 }
201 }
202 std::swap(m_data, new_data);
203 m_capacity_width = new_capacity_width;
204 m_capacity_height = new_capacity_height;
205 }
206 }
207 };
208
209 struct FTLibraryWrapper
210 {
FTLibraryWrapper__anon714a98a00111::FTLibraryWrapper211 FTLibraryWrapper()
212 {
213 if (!m_library && FT_Init_FreeType(&m_library)) // if no library exists and we can't create one...
214 throw FailedFTLibraryInit("Unable to initialize FreeType font library object");
215 }
~FTLibraryWrapper__anon714a98a00111::FTLibraryWrapper216 ~FTLibraryWrapper() { FT_Done_FreeType(m_library); }
217 FT_Library m_library = nullptr;
218 } g_library;
219
220 struct PushSubmatchOntoStackP
221 {
222 typedef void result_type;
operator ()__anon714a98a00111::PushSubmatchOntoStackP223 void operator()(const std::string* str,
224 std::stack<Font::Substring>& tag_stack,
225 bool& ignore_tags,
226 const boost::xpressive::ssub_match& sub) const
227 {
228 tag_stack.push(Font::Substring(*str, sub));
229 if (tag_stack.top() == PRE_TAG)
230 ignore_tags = true;
231 }
232 };
233 const boost::xpressive::function<PushSubmatchOntoStackP>::type PushP = {{}};
234
SetJustification(bool & last_line_of_curr_just,Font::LineData & line_data,Alignment orig_just,Alignment prev_just)235 void SetJustification(bool& last_line_of_curr_just, Font::LineData& line_data, Alignment orig_just, Alignment prev_just)
236 {
237 if (last_line_of_curr_just) {
238 line_data.justification = orig_just;
239 last_line_of_curr_just = false;
240 } else {
241 line_data.justification = prev_just;
242 }
243 }
244
245 const double ITALICS_SLANT_ANGLE = 12; // degrees
246 const double ITALICS_FACTOR = 1.0 / tan((90 - ITALICS_SLANT_ANGLE) * 3.1415926 / 180.0); // factor used to shear glyphs ITALICS_SLANT_ANGLE degrees CW from straight up
247
248 const std::vector<std::pair<std::uint32_t, std::uint32_t>> PRINTABLE_ASCII_ALPHA_RANGES{
249 {0x41, 0x5B},
250 {0x61, 0x7B}};
251
252 const std::vector<std::pair<std::uint32_t, std::uint32_t>> PRINTABLE_ASCII_NONALPHA_RANGES{
253 {0x09, 0x0D},
254 {0x20, 0x21},
255 {0x30, 0x3A},
256 {0x21, 0x30},
257 {0x3A, 0x41},
258 {0x5B, 0x61},
259 {0x7B, 0x7F}};
260 }
261
262 ///////////////////////////////////////
263 // Constants
264 ///////////////////////////////////////
265 const StrSize GG::S0(0);
266 const StrSize GG::S1(1);
267 const StrSize GG::INVALID_STR_SIZE(std::numeric_limits<std::size_t>::max());
268 const CPSize GG::CP0(0);
269 const CPSize GG::CP1(1);
270 const CPSize GG::INVALID_CP_SIZE(std::numeric_limits<std::size_t>::max());
271
272
273 ///////////////////////////////////////
274 // function GG::RgbaTag
275 ///////////////////////////////////////
RgbaTag(const Clr & c)276 std::string GG::RgbaTag(const Clr& c)
277 {
278 std::stringstream stream;
279 stream << "<rgba "
280 << static_cast<int>(c.r) << " "
281 << static_cast<int>(c.g) << " "
282 << static_cast<int>(c.b) << " "
283 << static_cast<int>(c.a)
284 << ">";
285 return stream.str();
286 }
287
288
289 ///////////////////////////////////////
290 // TextFormat
291 ///////////////////////////////////////
292 const TextFormat GG::FORMAT_NONE (0);
293 const TextFormat GG::FORMAT_VCENTER (1 << 0);
294 const TextFormat GG::FORMAT_TOP (1 << 1);
295 const TextFormat GG::FORMAT_BOTTOM (1 << 2);
296 const TextFormat GG::FORMAT_CENTER (1 << 3);
297 const TextFormat GG::FORMAT_LEFT (1 << 4);
298 const TextFormat GG::FORMAT_RIGHT (1 << 5);
299 const TextFormat GG::FORMAT_NOWRAP (1 << 6);
300 const TextFormat GG::FORMAT_WORDBREAK (1 << 7);
301 const TextFormat GG::FORMAT_LINEWRAP (1 << 8);
302 const TextFormat GG::FORMAT_IGNORETAGS (1 << 9);
303
304 GG_FLAGSPEC_IMPL(TextFormat);
305
306 namespace {
RegisterTextFormats()307 bool RegisterTextFormats()
308 {
309 FlagSpec<TextFormat>& spec = FlagSpec<TextFormat>::instance();
310 spec.insert(FORMAT_NONE, "FORMAT_NONE", true);
311 spec.insert(FORMAT_VCENTER, "FORMAT_VCENTER", true);
312 spec.insert(FORMAT_TOP, "FORMAT_TOP", true);
313 spec.insert(FORMAT_BOTTOM, "FORMAT_BOTTOM", true);
314 spec.insert(FORMAT_CENTER, "FORMAT_CENTER", true);
315 spec.insert(FORMAT_LEFT, "FORMAT_LEFT", true);
316 spec.insert(FORMAT_RIGHT, "FORMAT_RIGHT", true);
317 spec.insert(FORMAT_NOWRAP, "FORMAT_NOWRAP", true);
318 spec.insert(FORMAT_WORDBREAK, "FORMAT_WORDBREAK", true);
319 spec.insert(FORMAT_LINEWRAP, "FORMAT_LINEWRAP", true);
320 spec.insert(FORMAT_IGNORETAGS, "FORMAT_IGNORETAGS",true);
321 return true;
322 }
323 bool dummy = RegisterTextFormats();
324 }
325
326
327 ///////////////////////////////////////
328 // class GG::Font::Substring
329 ///////////////////////////////////////
330 const std::string Font::Substring::EMPTY_STRING;
331
Substring()332 Font::Substring::Substring() :
333 str(&EMPTY_STRING),
334 first(0),
335 second(0)
336 {}
337
Substring(const std::string & str_,std::string::const_iterator first_,std::string::const_iterator second_)338 Font::Substring::Substring(const std::string& str_,
339 std::string::const_iterator first_,
340 std::string::const_iterator second_) :
341 str(&str_),
342 first(0),
343 second(0)
344 {
345 assert(str->begin() <= first_);
346 assert(first_ <= second_);
347 assert(second_ <= str->end());
348 first = std::distance(str->begin(), first_);
349 second = std::distance(str->begin(), second_);
350 }
351
Substring(const std::string & str_,const IterPair & pair)352 Font::Substring::Substring(const std::string& str_, const IterPair& pair) :
353 str(&str_),
354 first(0),
355 second(0)
356 {
357 assert(str->begin() <= pair.first);
358 assert(pair.first <= pair.second);
359 assert(pair.second <= str->end());
360 first = std::distance(str->begin(), pair.first);
361 second = std::distance(str->begin(), pair.second);
362 }
363
Bind(const std::string & str_)364 void Font::Substring::Bind(const std::string& str_)
365 {
366 assert(std::distance(str_.begin(), str_.end()) >= second);
367 str = &str_;
368 }
369
begin() const370 std::string::const_iterator Font::Substring::begin() const
371 { return std::next(str->begin(), first); }
372
end() const373 std::string::const_iterator Font::Substring::end() const
374 { return std::next(str->begin(), second); }
375
empty() const376 bool Font::Substring::empty() const
377 { return first == second; }
378
size() const379 std::size_t Font::Substring::size() const
380 { return second - first; }
381
operator std::string() const382 Font::Substring::operator std::string() const
383 { return std::string(begin(), end()); }
384
operator ==(const std::string & rhs) const385 bool Font::Substring::operator==(const std::string& rhs) const
386 { return size() == rhs.size() && !std::memcmp(str->data() + first, rhs.data(), rhs.size()); }
387
operator !=(const std::string & rhs) const388 bool Font::Substring::operator!=(const std::string& rhs) const
389 { return !operator==(rhs); }
390
operator +=(const IterPair & rhs)391 Font::Substring& Font::Substring::operator+=(const IterPair& rhs)
392 {
393 assert(rhs.first <= rhs.second);
394 assert(std::distance(str->begin(), rhs.first) == second);
395 second = std::distance(str->begin(), rhs.second);
396 return *this;
397 }
398
399
400 ///////////////////////////////////////
401 // Free Functions
402 ///////////////////////////////////////
operator <<(std::ostream & os,const Font::Substring & substr)403 std::ostream& GG::operator<<(std::ostream& os, const Font::Substring& substr)
404 {
405 std::ostream_iterator<char> out_it(os);
406 std::copy(substr.begin(), substr.end(), out_it);
407 return os;
408 }
409
CodePointIndexOf(std::size_t line,CPSize index,const std::vector<Font::LineData> & line_data)410 CPSize GG::CodePointIndexOf(std::size_t line, CPSize index,
411 const std::vector<Font::LineData>& line_data)
412 {
413 CPSize retval(0);
414 if (line_data.size() <= line) {
415 auto it = line_data.rbegin();
416 auto end_it = line_data.rend();
417 while (it != end_it) {
418 if (!it->char_data.empty()) {
419 retval = it->char_data.back().code_point_index + 1;
420 break;
421 }
422 ++it;
423 }
424 } else if (index < line_data[line].char_data.size()) {
425 retval = line_data[line].char_data[Value(index)].code_point_index;
426 } else {
427 auto it = line_data.rbegin() + (line_data.size() - 1 - line);
428 auto end_it = line_data.rend();
429 while (it != end_it) {
430 if (!it->char_data.empty()) {
431 retval = it->char_data.back().code_point_index + 1;
432 break;
433 }
434 ++it;
435 }
436 }
437 return retval;
438 }
439
StringIndexOf(std::size_t line,CPSize index,const std::vector<Font::LineData> & line_data)440 StrSize GG::StringIndexOf(std::size_t line, CPSize index,
441 const std::vector<Font::LineData>& line_data)
442 {
443 StrSize retval(0);
444 if (line_data.size() <= line) {
445 auto it = line_data.rbegin();
446 auto end_it = line_data.rend();
447 while (it != end_it) {
448 if (!it->char_data.empty()) {
449 retval = it->char_data.back().string_index + it->char_data.back().string_size;
450 break;
451 }
452 ++it;
453 }
454 } else if (index < line_data[line].char_data.size()) {
455 retval = line_data[line].char_data[Value(index)].string_index;
456 } else {
457 auto it = line_data.rbegin() + (line_data.size() - 1 - line);
458 auto end_it = line_data.rend();
459 while (it != end_it) {
460 if (!it->char_data.empty()) {
461 retval = it->char_data.back().string_index + it->char_data.back().string_size;
462 break;
463 }
464 ++it;
465 }
466 }
467 return retval;
468 }
469
LinePositionOf(CPSize index,const std::vector<Font::LineData> & line_data)470 std::pair<std::size_t, CPSize> GG::LinePositionOf(
471 CPSize index, const std::vector<Font::LineData>& line_data)
472 {
473 std::pair<std::size_t, CPSize> retval(std::numeric_limits<std::size_t>::max(),
474 INVALID_CP_SIZE);
475 for (std::size_t i = 0; i < line_data.size(); ++i) {
476 const auto& char_data = line_data[i].char_data;
477 if (!char_data.empty() &&
478 char_data.front().code_point_index <= index &&
479 index <= char_data.back().code_point_index)
480 {
481 retval.first = i;
482 retval.second = index - char_data.front().code_point_index;
483 break;
484 }
485 }
486 return retval;
487 }
488
489 namespace {
490 namespace xpr = boost::xpressive;
491
492 /** CompiledRegex maintains a compiled boost::xpressive regular
493 expression that includes a tag stack which can be cleared and
494 provided to callers without the overhead of recompiling the
495 regular expression.*/
496 class CompiledRegex {
497 public:
CompiledRegex(const std::unordered_set<std::string> & known_tags,bool strip_unpaired_tags)498 CompiledRegex(const std::unordered_set<std::string>& known_tags,
499 bool strip_unpaired_tags) :
500 m_known_tags(&known_tags)
501 {
502 // Synonyms for s1 thru s5 sub matches
503 xpr::mark_tag tag_name_tag(1);
504 xpr::mark_tag open_bracket_tag(2);
505 xpr::mark_tag close_bracket_tag(3);
506 xpr::mark_tag whitespace_tag(4);
507 xpr::mark_tag text_tag(5);
508
509 #if BOOST_VERSION >= 106000
510 using boost::placeholders::_1;
511 #endif
512
513 // The comments before each regex are intended to clarify the mapping from xpressive
514 // notation to the more typical regex notation. If you read xpressive or don't read
515 // regex then ignore them.
516
517 // -+ 'non-greedy', ~ 'not', set[|] 'set', _s 'space' = 'anything but space or <'
518 const xpr::sregex TAG_PARAM =
519 -+~xpr::set[xpr::_s | '<'];
520
521 //+_w one or more greed word chars, () group no capture, [] semantic operation
522 const xpr::sregex OPEN_TAG_NAME =
523 (+xpr::_w)[xpr::check(boost::bind(&CompiledRegex::MatchesKnownTag, this, _1))];
524
525 // (+_w) one or more greedy word check matches stack
526 const xpr::sregex CLOSE_TAG_NAME =
527 (+xpr::_w)[xpr::check(boost::bind(&CompiledRegex::MatchesTopOfStack, this, _1))];
528
529 // *blank 'zero or more greedy whitespace', >> 'followed by', _ln 'newline',
530 // (set = 'a', 'b') is '[ab]', +blank 'one or more greedy blank'
531 const xpr::sregex WHITESPACE =
532 (*xpr::blank >> (xpr::_ln | (xpr::set = '\n', '\r', '\f'))) | +xpr::blank;
533
534 // < followed by not space or < or one or more not space or <
535 const xpr::sregex TEXT =
536 ('<' >> *~xpr::set[xpr::_s | '<']) | (+~xpr::set[xpr::_s | '<']);
537
538 if (!strip_unpaired_tags) {
539 m_EVERYTHING =
540 ('<' >> (tag_name_tag = OPEN_TAG_NAME) // < followed by TAG_NAME
541 >> xpr::repeat<0, 9>(+xpr::blank >> TAG_PARAM) // repeat 0 to 9 a single blank followed
542 // by TAG_PARAM
543 >> (open_bracket_tag.proto_base() = '>')) // s1. close tag and push operation
544 [PushP(xpr::ref(m_text), xpr::ref(m_tag_stack), xpr::ref(m_ignore_tags), tag_name_tag)] |
545 ("</" >> (tag_name_tag = CLOSE_TAG_NAME) >> (close_bracket_tag.proto_base() = '>')) |
546 (whitespace_tag = WHITESPACE) |
547 (text_tag = TEXT);
548 } else {
549 // don't care about matching with tag stack when
550 // matching close tags, or updating the stack when
551 // matching open tags
552 m_EVERYTHING =
553 ('<' >> OPEN_TAG_NAME >> xpr::repeat<0, 9>(+xpr::blank >> TAG_PARAM) >> '>') |
554 ("</" >> OPEN_TAG_NAME >> '>') |
555 (whitespace_tag = WHITESPACE) |
556 (text_tag = TEXT);
557 }
558 }
559
BindRegexToText(const std::string & new_text,bool ignore_tags)560 xpr::sregex& BindRegexToText(const std::string& new_text, bool ignore_tags) {
561 if (!m_tag_stack.empty()) {
562 std::stack<Font::Substring> empty_stack;
563 std::swap(m_tag_stack, empty_stack);
564 }
565 m_text = &new_text;
566 m_ignore_tags = ignore_tags;
567 return m_EVERYTHING;
568 }
569
570 private:
571
MatchesKnownTag(const boost::xpressive::ssub_match & sub)572 bool MatchesKnownTag(const boost::xpressive::ssub_match& sub) {
573 return m_ignore_tags ? false : m_known_tags->count(sub.str()) != 0;
574 }
575
MatchesTopOfStack(const boost::xpressive::ssub_match & sub)576 bool MatchesTopOfStack(const boost::xpressive::ssub_match& sub) {
577 bool retval = m_tag_stack.empty() ? false : m_tag_stack.top() == sub;
578 if (retval) {
579 m_tag_stack.pop();
580 if (m_tag_stack.empty() || m_tag_stack.top() != PRE_TAG)
581 m_ignore_tags = false;
582 }
583 return retval;
584 }
585
586 const std::string* m_text = nullptr;
587 const std::unordered_set<std::string>* m_known_tags = nullptr;
588 bool m_ignore_tags = false;
589
590 // m_tag_stack is used to track XML opening/closing tags.
591 std::stack<Font::Substring> m_tag_stack;
592
593 // The combined regular expression.
594 xpr::sregex m_EVERYTHING;
595 };
596
597 /** TagHandler stores a set of all known tags and provides pre-compiled regexs for those tags.
598
599 Known tags are tags that will be parsed into TextElement OPEN_TAG or CLOSE_TAG. */
600 class TagHandler {
601 public:
TagHandler()602 TagHandler() :
603 m_known_tags(),
604 m_regex_w_tags(m_known_tags, false),
605 m_regex_w_tags_skipping_unmatched(m_known_tags, true)
606 {}
607
608 /** Add a tag to the set of known tags.*/
Insert(const std::string & tag)609 void Insert(const std::string& tag)
610 { m_known_tags.insert(tag); }
611
612 /** Remove a tag from the set of known tags.*/
Erase(const std::string & tag)613 void Erase(const std::string& tag)
614 { m_known_tags.erase(tag); }
615
616 /** Remove all tags from the set of known tags.*/
Clear()617 void Clear()
618 { m_known_tags.clear(); }
619
IsKnown(const std::string & tag) const620 bool IsKnown(const std::string &tag) const
621 { return m_known_tags.count(tag); }
622
623 // Return a regex bound to \p text using the currently known
624 // tags. If required \p ignore_tags and/or \p strip_unpaired_tags.
Regex(const std::string & text,bool ignore_tags,bool strip_unpaired_tags=false)625 xpr::sregex& Regex(const std::string& text, bool ignore_tags, bool strip_unpaired_tags = false)
626 {
627 if (!strip_unpaired_tags)
628 return m_regex_w_tags.BindRegexToText(text, ignore_tags);
629 else
630 return m_regex_w_tags_skipping_unmatched.BindRegexToText(text, ignore_tags);
631 }
632
633 private:
634 // set of tags known to the handler
635 std::unordered_set<std::string> m_known_tags;
636
637 // Compiled regular expression including tag stack
638 CompiledRegex m_regex_w_tags;
639
640 // Compiled regular expression using tags but skipping unmatched tags.
641 CompiledRegex m_regex_w_tags_skipping_unmatched;
642 };
643
644
645 // Global set of known tags. Wrapped in a function for deterministic static initialization order.
StaticTagHandler()646 TagHandler& StaticTagHandler()
647 {
648 static TagHandler tag_handler;
649 return tag_handler;
650 }
651
652 // Registers the default action and known tags.
RegisterDefaultTags()653 int RegisterDefaultTags()
654 {
655 StaticTagHandler().Insert(ITALIC_TAG);
656 StaticTagHandler().Insert(SHADOW_TAG);
657 StaticTagHandler().Insert(UNDERLINE_TAG);
658 StaticTagHandler().Insert(SUPERSCRIPT_TAG);
659 StaticTagHandler().Insert(SUBSCRIPT_TAG);
660 StaticTagHandler().Insert(RGBA_TAG);
661 StaticTagHandler().Insert(ALIGN_LEFT_TAG);
662 StaticTagHandler().Insert(ALIGN_CENTER_TAG);
663 StaticTagHandler().Insert(ALIGN_RIGHT_TAG);
664 StaticTagHandler().Insert(PRE_TAG);
665
666 // Must have return value to call at static initialization time.
667 return 0;
668 }
669
670 // Register the default tags at static initialization time.
671 int register_tags_dummy = RegisterDefaultTags();
672 }
673
674 ///////////////////////////////////////
675 // class GG::Font::TextElement
676 ///////////////////////////////////////
TextElement()677 Font::TextElement::TextElement() :
678 whitespace(false),
679 newline(false),
680 cached_width(-X1)
681 {}
682
TextElement(bool ws,bool nl)683 Font::TextElement::TextElement(bool ws, bool nl) :
684 whitespace(ws),
685 newline(nl),
686 cached_width(-X1)
687 {}
688
~TextElement()689 Font::TextElement::~TextElement()
690 {}
691
Bind(const std::string & whole_text)692 void Font::TextElement::Bind(const std::string& whole_text)
693 { text.Bind(whole_text); }
694
Type() const695 Font::TextElement::TextElementType Font::TextElement::Type() const
696 { return newline ? NEWLINE : (whitespace ? WHITESPACE : TEXT); }
697
Width() const698 X Font::TextElement::Width() const
699 {
700 if (cached_width == -X1)
701 cached_width = std::accumulate(widths.begin(), widths.end(), X0);
702 return cached_width;
703 }
704
StringSize() const705 StrSize Font::TextElement::StringSize() const
706 { return StrSize(text.size()); }
707
CodePointSize() const708 CPSize Font::TextElement::CodePointSize() const
709 { return CPSize(widths.size()); }
710
711
operator ==(const TextElement & rhs) const712 bool Font::TextElement::operator==(const TextElement &rhs) const
713 {
714 return (text == rhs.text && widths == rhs.widths
715 && whitespace == rhs.whitespace && newline == rhs.newline);
716 }
717
718 ///////////////////////////////////////
719 // class GG::Font::FormattingTag
720 ///////////////////////////////////////
FormattingTag()721 Font::FormattingTag::FormattingTag() :
722 TextElement(),
723 close_tag(false)
724 {}
725
FormattingTag(bool close)726 Font::FormattingTag::FormattingTag(bool close) :
727 TextElement(false, false),
728 close_tag(close)
729 {}
730
Bind(const std::string & whole_text)731 void Font::FormattingTag::Bind(const std::string& whole_text)
732 {
733 TextElement::Bind(whole_text);
734 tag_name.Bind(whole_text);
735 for (Substring& substring : params) {
736 substring.Bind(whole_text);
737 }
738 }
739
Type() const740 Font::FormattingTag::TextElementType Font::FormattingTag::Type() const
741 { return close_tag ? CLOSE_TAG : OPEN_TAG; }
742
operator ==(const TextElement & rhs) const743 bool Font::FormattingTag::operator==(const TextElement &rhs) const
744 {
745 Font::FormattingTag const* ft = dynamic_cast<Font::FormattingTag const*>(&rhs);
746 return (ft
747 && Font::TextElement::operator==(rhs)
748 && params == ft->params
749 && tag_name == ft->tag_name
750 && close_tag == ft->close_tag);
751 }
752
753 ///////////////////////////////////////
754 // class GG::Font::TextAndElementsAssembler
755 ///////////////////////////////////////
756 class Font::TextAndElementsAssembler::Impl
757 {
758 public:
Impl(const Font & font)759 Impl(const Font& font) :
760 m_font(font),
761 m_text(),
762 m_text_elements(),
763 m_are_widths_calculated(false)
764 {}
765
766 /** Return the constructed text.*/
Text() const767 const std::string& Text() const
768 { return m_text; }
769
770 /** Return the constructed TextElements.*/
Elements()771 const std::vector<std::shared_ptr<TextElement>>& Elements()
772 {
773 if (!m_are_widths_calculated)
774 m_font.FillTemplatedText(m_text, m_text_elements, m_text_elements.begin());
775
776 return m_text_elements;
777 }
778
779 /** Add an open tag iff it exists as a recognized tag.*/
AddOpenTag(const std::string & tag,const std::vector<std::string> * params=nullptr)780 void AddOpenTag(const std::string& tag, const std::vector<std::string>* params = nullptr)
781 {
782 if (!StaticTagHandler().IsKnown(tag))
783 return;
784
785 m_are_widths_calculated = false;
786
787 // Create the opening part of an open tag, like this: "<tag"
788 auto element = std::make_shared<Font::FormattingTag>(false);
789 size_t tag_begin = m_text.size();
790 size_t tag_name_begin = m_text.append("<").size();
791 size_t tag_name_end = m_text.append(tag).size();
792 element->tag_name = Substring(m_text,
793 std::next(m_text.begin(), tag_name_begin),
794 std::next(m_text.begin(), tag_name_end));
795
796 // If there are params add them, like this: "<tag param1 param2"
797 if (params) {
798 for (const std::string& param : *params) {
799 m_text.append(" ");
800 size_t param_begin = m_text.size();
801 size_t param_end = m_text.append(param).size();
802
803 element->params.push_back(Substring(m_text,
804 std::next(m_text.begin(), param_begin),
805 std::next(m_text.begin(), param_end)));
806 }
807 }
808
809 // Create the close part of an open tag to complete the tag, like this:"<tag param1 param2>"
810 size_t tag_end = m_text.append(">").size();
811 element->text = Substring(m_text,
812 std::next(m_text.begin(), tag_begin),
813 std::next(m_text.begin(), tag_end));
814
815 m_text_elements.push_back(element);
816 }
817
818 /** Add a close tag iff it exists as a recognized tag.*/
AddCloseTag(const std::string & tag)819 void AddCloseTag(const std::string& tag)
820 {
821 if (!StaticTagHandler().IsKnown(tag))
822 return;
823
824 m_are_widths_calculated = false;
825
826 // Create a close tag that looks like this: "</tag>"
827 auto element = std::make_shared<Font::FormattingTag>(true);
828 size_t tag_begin = m_text.size();
829 size_t tag_name_begin = m_text.append("</").size();
830 size_t tag_name_end = m_text.append(tag).size();
831 size_t tag_end = m_text.append(">").size();
832 element->text = Substring(m_text,
833 std::next(m_text.begin(), tag_begin),
834 std::next(m_text.begin(), tag_end));
835
836 element->tag_name = Substring(m_text,
837 std::next(m_text.begin(), tag_name_begin),
838 std::next(m_text.begin(), tag_name_end));
839 m_text_elements.push_back(element);
840 }
841
842 /** Add a text element. Any whitespace in this text element will be non-breaking.*/
AddText(const std::string & text)843 void AddText(const std::string& text)
844 {
845 m_are_widths_calculated = false;
846
847 auto element = std::make_shared<Font::TextElement>(false, false);
848 size_t begin = m_text.size();
849 size_t end = m_text.append(text).size();
850 element->text = Substring(m_text,
851 std::next(m_text.begin(), begin),
852 std::next(m_text.begin(), end));
853 m_text_elements.push_back(element);
854 }
855
856 /** Add a white space element.*/
AddWhitespace(const std::string & whitespace)857 void AddWhitespace(const std::string& whitespace)
858 {
859 m_are_widths_calculated = false;
860
861 auto element = std::make_shared<Font::TextElement>(true, false);
862 size_t begin = m_text.size();
863 size_t end = m_text.append(whitespace).size();
864 element->text = Substring(m_text,
865 std::next(m_text.begin(), begin),
866 std::next(m_text.begin(), end));
867 m_text_elements.push_back(element);
868 }
869
870 /** Add a newline element.*/
AddNewline()871 void AddNewline()
872 {
873 m_are_widths_calculated = false;
874
875 m_text_elements.push_back(std::make_shared<Font::TextElement>(false, true));
876 }
877
878 /** Add open color tag.*/
AddOpenTag(const Clr & color)879 void AddOpenTag(const Clr& color)
880 {
881 std::vector<std::string> params = { std::to_string(color.r),
882 std::to_string(color.g),
883 std::to_string(color.b),
884 std::to_string(color.a) };
885
886 AddOpenTag("rgba", ¶ms);
887 }
888
889 private:
890 const Font& m_font;
891 std::string m_text;
892 std::vector<std::shared_ptr<TextElement>> m_text_elements;
893 bool m_are_widths_calculated;
894 };
895
896
TextAndElementsAssembler(const Font & font)897 Font::TextAndElementsAssembler::TextAndElementsAssembler(const Font& font) :
898 m_impl(new Impl(font))
899 {}
900
901 // Required because Impl is defined here
~TextAndElementsAssembler()902 Font::TextAndElementsAssembler::~TextAndElementsAssembler()
903 {}
904
Text()905 const std::string& Font::TextAndElementsAssembler::Text()
906 { return m_impl->Text(); }
907
Elements()908 const std::vector<std::shared_ptr<Font::TextElement>>& Font::TextAndElementsAssembler::Elements()
909 { return m_impl->Elements(); }
910
AddOpenTag(const std::string & tag)911 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddOpenTag(const std::string& tag)
912 {
913 m_impl->AddOpenTag(tag);
914 return *this;
915 }
916
AddOpenTag(const std::string & tag,const std::vector<std::string> & params)917 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddOpenTag(
918 const std::string& tag, const std::vector<std::string>& params)
919 {
920 m_impl->AddOpenTag(tag, ¶ms);
921 return *this;
922 }
923
AddCloseTag(const std::string & tag)924 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddCloseTag(const std::string& tag)
925 {
926 m_impl->AddCloseTag(tag);
927 return *this;
928 }
929
AddText(const std::string & text)930 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddText(const std::string& text)
931 {
932 m_impl->AddText(text);
933 return *this;
934 }
935
AddWhitespace(const std::string & whitespace)936 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddWhitespace(const std::string& whitespace)
937 {
938 m_impl->AddWhitespace(whitespace);
939 return *this;
940 }
941
AddNewline()942 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddNewline()
943 {
944 m_impl->AddNewline();
945 return *this;
946 }
947
AddOpenTag(const Clr & color)948 Font::TextAndElementsAssembler& Font::TextAndElementsAssembler::AddOpenTag(const Clr& color)
949 {
950 m_impl->AddOpenTag(color);
951 return *this;
952 }
953
954 ///////////////////////////////////////
955 // class GG::Font::LineData
956 ///////////////////////////////////////
LineData()957 Font::LineData::LineData() :
958 justification(ALIGN_CENTER)
959 {}
960
Width() const961 X Font::LineData::Width() const
962 { return char_data.empty() ? X0 : char_data.back().extent; }
963
Empty() const964 bool Font::LineData::Empty() const
965 { return char_data.empty(); }
966
967 ///////////////////////////////////////
968 // class GG::Font::RenderState
969 ///////////////////////////////////////
RenderState()970 Font::RenderState::RenderState() :
971 use_italics(0),
972 use_shadow(0),
973 draw_underline(0),
974 super_sub_shift(0)
975 {
976 // Initialize the color stack with the current color
977 GLfloat current[4];
978 glGetFloatv(GL_CURRENT_COLOR, current);
979 PushColor(current[0]*255, current[1]*255, current[2]*255, current[3]*255);
980 }
981
RenderState(Clr color)982 Font::RenderState::RenderState (Clr color):
983 use_italics(0),
984 use_shadow(0),
985 draw_underline(0),
986 super_sub_shift(0)
987 {
988 PushColor(color.r, color.g, color.b, color.a);
989 }
990
PushColor(GLubyte r,GLubyte g,GLubyte b,GLubyte a)991 void Font::RenderState::PushColor(GLubyte r, GLubyte g, GLubyte b, GLubyte a)
992 {
993 Clr color(r, g, b, a);
994 // The same color may end up being stored multiple times, but the cost of deduplication
995 // is greater than the cost of just letting it be so.
996 color_index_stack.push(used_colors.size());
997 used_colors.push_back(color);
998 }
999
PopColor()1000 void Font::RenderState::PopColor()
1001 {
1002 // Never remove the initial color from the stack
1003 if (color_index_stack.size() > 1)
1004 color_index_stack.pop();
1005 }
1006
CurrentIndex() const1007 int Font::RenderState::CurrentIndex() const
1008 { return color_index_stack.top(); }
1009
CurrentColor() const1010 const Clr& Font::RenderState::CurrentColor() const
1011 { return used_colors[CurrentIndex()]; }
1012
ColorsEmpty() const1013 bool Font::RenderState::ColorsEmpty() const
1014 { return color_index_stack.size() <= 1; }
1015
1016
1017 ///////////////////////////////////////
1018 // class GG::Font::RenderCache
1019 ///////////////////////////////////////
1020
1021 // Must be here for scoped_ptr deleter to work
RenderCache()1022 Font::RenderCache::RenderCache() :
1023 vertices(new GL2DVertexBuffer()),
1024 coordinates(new GLTexCoordBuffer()),
1025 colors(new GLRGBAColorBuffer()),
1026 underline_vertices(new GL2DVertexBuffer()),
1027 underline_colors(new GLRGBAColorBuffer())
1028 {}
1029
1030 // Must be here for scoped_ptr deleter to work
~RenderCache()1031 Font::RenderCache::~RenderCache()
1032 {}
1033
1034 ///////////////////////////////////////
1035 // class GG::Font::LineData::CharData
1036 ///////////////////////////////////////
CharData()1037 Font::LineData::CharData::CharData() :
1038 extent(0),
1039 string_index(0)
1040 {}
1041
CharData(X extent_,StrSize str_index,StrSize str_size,CPSize cp_index,const std::vector<std::shared_ptr<TextElement>> & tags_)1042 Font::LineData::CharData::CharData(X extent_, StrSize str_index, StrSize str_size, CPSize cp_index,
1043 const std::vector<std::shared_ptr<TextElement>>& tags_) :
1044 extent(extent_),
1045 string_index(str_index),
1046 string_size(str_size),
1047 code_point_index(cp_index),
1048 tags()
1049 {
1050 for (auto tag : tags_) {
1051 tags.push_back(std::dynamic_pointer_cast<FormattingTag>(tag));
1052 }
1053 }
1054
1055
1056 ///////////////////////////////////////
1057 // struct GG::Font::Glyph
1058 ///////////////////////////////////////
Glyph()1059 Font::Glyph::Glyph() :
1060 y_offset(0),
1061 left_bearing(0),
1062 advance(0),
1063 width(0)
1064 {}
1065
Glyph(const std::shared_ptr<Texture> & texture,const Pt & ul,const Pt & lr,Y y_ofs,X lb,X adv)1066 Font::Glyph::Glyph(const std::shared_ptr<Texture>& texture, const Pt& ul, const Pt& lr, Y y_ofs, X lb, X adv) :
1067 sub_texture(texture, ul.x, ul.y, lr.x, lr.y),
1068 y_offset(y_ofs),
1069 left_bearing(lb),
1070 advance(adv),
1071 width(ul.x - lr.x)
1072 {}
1073
1074 ///////////////////////////////////////
1075 // class GG::Font
1076 ///////////////////////////////////////
1077
Font()1078 Font::Font() :
1079 m_pt_sz(0),
1080 m_ascent(0),
1081 m_descent(0),
1082 m_height(0),
1083 m_lineskip(0),
1084 m_underline_offset(0.0),
1085 m_underline_height(0.0),
1086 m_italics_offset(0.0),
1087 m_super_sub_offset(0.0),
1088 m_shadow_offset(0.0),
1089 m_space_width(0)
1090 {}
1091
Font(const std::string & font_filename,unsigned int pts)1092 Font::Font(const std::string& font_filename, unsigned int pts) :
1093 m_font_filename(font_filename),
1094 m_pt_sz(pts),
1095 m_ascent(0),
1096 m_descent(0),
1097 m_height(0),
1098 m_lineskip(0),
1099 m_underline_offset(0.0),
1100 m_underline_height(0.0),
1101 m_italics_offset(0.0),
1102 m_shadow_offset(0.0),
1103 m_space_width(0)
1104 {
1105 if (!m_font_filename.empty()) {
1106 detail::FTFaceWrapper wrapper;
1107 FT_Error error = GetFace(wrapper.m_face);
1108 CheckFace(wrapper.m_face, error);
1109 Init(wrapper.m_face);
1110 }
1111 }
1112
Font(const std::string & font_filename,unsigned int pts,const std::vector<unsigned char> & file_contents)1113 Font::Font(const std::string& font_filename, unsigned int pts,
1114 const std::vector<unsigned char>& file_contents) :
1115 m_font_filename(font_filename),
1116 m_pt_sz(pts),
1117 m_ascent(0),
1118 m_descent(0),
1119 m_height(0),
1120 m_lineskip(0),
1121 m_underline_offset(0.0),
1122 m_underline_height(0.0),
1123 m_italics_offset(0.0),
1124 m_shadow_offset(0.0),
1125 m_space_width(0)
1126 {
1127 assert(!file_contents.empty());
1128 detail::FTFaceWrapper wrapper;
1129 FT_Error error = GetFace(file_contents, wrapper.m_face);
1130 CheckFace(wrapper.m_face, error);
1131 Init(wrapper.m_face);
1132 }
1133
~Font()1134 Font::~Font()
1135 {}
1136
FontName() const1137 const std::string& Font::FontName() const
1138 { return m_font_filename; }
1139
PointSize() const1140 unsigned int Font::PointSize() const
1141 { return m_pt_sz; }
1142
UnicodeCharsets() const1143 const std::vector<UnicodeCharset>& Font::UnicodeCharsets() const
1144 { return m_charsets; }
1145
Ascent() const1146 Y Font::Ascent() const
1147 { return m_ascent; }
1148
Descent() const1149 Y Font::Descent() const
1150 { return m_descent; }
1151
Height() const1152 Y Font::Height() const
1153 { return m_height; }
1154
Lineskip() const1155 Y Font::Lineskip() const
1156 { return m_lineskip; }
1157
SpaceWidth() const1158 X Font::SpaceWidth() const
1159 { return m_space_width; }
1160
RenderText(const Pt & pt_,const std::string & text) const1161 X Font::RenderText(const Pt& pt_, const std::string& text) const
1162 {
1163 Pt pt = pt_;
1164 X orig_x = pt.x;
1165
1166 double orig_color[4];
1167 glGetDoublev(GL_CURRENT_COLOR, orig_color);
1168 glBindTexture(GL_TEXTURE_2D, m_texture->OpenGLId());
1169
1170 RenderCache cache;
1171 RenderState render_state;
1172
1173 for (auto text_it = text.begin(); text_it != text.end();) {
1174 std::uint32_t c = utf8::next(text_it, text.end());
1175 auto it = m_glyphs.find(c);
1176 if (it == m_glyphs.end()) {
1177 pt.x += m_space_width; // move forward by the extent of the character when a whitespace or unprintable glyph is requested
1178 } else {
1179 pt.x += StoreGlyph(pt, it->second, &render_state, cache);
1180 }
1181 }
1182
1183 cache.vertices->createServerBuffer();
1184 cache.coordinates->createServerBuffer();
1185 cache.colors->createServerBuffer();
1186 RenderCachedText(cache);
1187
1188 return pt.x - orig_x;
1189 }
1190
RenderText(const Pt & ul,const Pt & lr,const std::string & text,Flags<TextFormat> & format,const std::vector<LineData> & line_data,RenderState * render_state) const1191 void Font::RenderText(const Pt& ul, const Pt& lr, const std::string& text, Flags<TextFormat>& format,
1192 const std::vector<LineData>& line_data, RenderState* render_state/* = 0*/) const
1193 {
1194 RenderState state;
1195 if (!render_state)
1196 render_state = &state;
1197
1198 RenderText(ul, lr, text, format, line_data, *render_state,
1199 0, CP0, line_data.size(),
1200 line_data.empty() ? CP0 : CPSize(line_data.back().char_data.size()));
1201 }
1202
RenderText(const Pt & ul,const Pt & lr,const std::string & text,Flags<TextFormat> & format,const std::vector<LineData> & line_data,RenderState & render_state,std::size_t begin_line,CPSize begin_char,std::size_t end_line,CPSize end_char) const1203 void Font::RenderText(const Pt& ul, const Pt& lr, const std::string& text, Flags<TextFormat>& format,
1204 const std::vector<LineData>& line_data, RenderState& render_state,
1205 std::size_t begin_line, CPSize begin_char,
1206 std::size_t end_line, CPSize end_char) const
1207 {
1208 RenderCache cache;
1209 PreRenderText(ul, lr, text, format, line_data, render_state, begin_line, begin_char, end_line, end_char, cache);
1210 RenderCachedText(cache);
1211 }
1212
PreRenderText(const Pt & ul,const Pt & lr,const std::string & text,Flags<TextFormat> & format,RenderCache & cache,const std::vector<LineData> & line_data,RenderState * render_state) const1213 void Font::PreRenderText(const Pt& ul, const Pt& lr, const std::string& text, Flags<TextFormat>& format, RenderCache& cache,
1214 const std::vector<LineData>& line_data, RenderState* render_state/* = 0*/) const
1215 {
1216 RenderState state;
1217 if (!render_state)
1218 render_state = &state;
1219
1220 PreRenderText(ul, lr, text, format, line_data, *render_state,
1221 0, CP0, line_data.size(), line_data.empty() ? CP0 : CPSize(line_data.back().char_data.size()), cache);
1222 }
1223
PreRenderText(const Pt & ul,const Pt & lr,const std::string & text,Flags<TextFormat> & format,const std::vector<LineData> & line_data,RenderState & render_state,std::size_t begin_line,CPSize begin_char,std::size_t end_line,CPSize end_char,RenderCache & cache) const1224 void Font::PreRenderText(const Pt& ul, const Pt& lr, const std::string& text, Flags<TextFormat>& format,
1225 const std::vector<LineData>& line_data, RenderState& render_state,
1226 std::size_t begin_line, CPSize begin_char,
1227 std::size_t end_line, CPSize end_char,
1228 RenderCache& cache) const
1229 {
1230 double orig_color[4];
1231 glGetDoublev(GL_CURRENT_COLOR, orig_color);
1232 //glBindTexture(GL_TEXTURE_2D, m_texture->OpenGLId());
1233
1234 Y y_origin = ul.y; // default value for FORMAT_TOP
1235 if (format & FORMAT_BOTTOM)
1236 y_origin = lr.y - (static_cast<int>(end_line - begin_line - 1) * m_lineskip + m_height);
1237 else if (format & FORMAT_VCENTER)
1238 y_origin = ul.y + ((lr.y - ul.y) - (static_cast<int>(end_line - begin_line - 1) * m_lineskip + m_height)) / 2.0;
1239
1240 for (std::size_t i = begin_line; i < end_line; ++i) {
1241 const LineData& line = line_data[i];
1242 X x_origin = ul.x; // default value for FORMAT_LEFT
1243 if (line.justification == ALIGN_RIGHT)
1244 x_origin = lr.x - line.Width();
1245 else if (line.justification == ALIGN_CENTER)
1246 x_origin = ul.x + ((lr.x - ul.x) - line.Width()) / 2.0;
1247 Y y = y_origin + static_cast<int>(i - begin_line) * m_lineskip;
1248 X x = x_origin;
1249
1250 CPSize start = CP0;
1251 if (i == begin_line)
1252 start = std::max(CP0, std::min(begin_char, CPSize(line.char_data.size() - 1)));
1253 CPSize end = CPSize(line.char_data.size());
1254 if (i == end_line - 1)
1255 end = std::max(CP0, std::min(end_char, CPSize(line.char_data.size())));
1256
1257 auto string_end_it = text.end();
1258 for (CPSize j = start; j < end; ++j) {
1259 const auto& char_data = line.char_data[Value(j)];
1260 for (auto tag : char_data.tags) {
1261 HandleTag(tag, orig_color, render_state);
1262 }
1263 std::uint32_t c = utf8::peek_next(text.begin() + Value(char_data.string_index), string_end_it);
1264 assert((text[Value(char_data.string_index)] == '\n') == (c == WIDE_NEWLINE));
1265 if (c == WIDE_NEWLINE)
1266 continue;
1267 auto it = m_glyphs.find(c);
1268 if (it == m_glyphs.end()) {
1269 x = x_origin + char_data.extent; // move forward by the extent of the character when a whitespace or unprintable glyph is requested
1270 } else {
1271 x += StoreGlyph(Pt(x, y), it->second, &render_state, cache);
1272 }
1273 }
1274 }
1275
1276 cache.vertices->createServerBuffer();
1277 cache.coordinates->createServerBuffer();
1278 cache.colors->createServerBuffer();
1279 }
1280
RenderCachedText(RenderCache & cache) const1281 void Font::RenderCachedText(RenderCache& cache) const
1282 {
1283 glBindTexture(GL_TEXTURE_2D, m_texture->OpenGLId());
1284
1285 glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1286 glEnableClientState(GL_VERTEX_ARRAY);
1287 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1288 glEnableClientState(GL_COLOR_ARRAY);
1289
1290 cache.vertices->activate();
1291 cache.coordinates->activate();
1292 cache.colors->activate();
1293 glDrawArrays(GL_QUADS, 0, cache.vertices->size());
1294
1295 glBindTexture(GL_TEXTURE_2D, 0);
1296 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1297
1298 cache.underline_vertices->activate();
1299 cache.underline_colors->activate();
1300 glDrawArrays(GL_QUADS, 0, cache.underline_vertices->size());
1301
1302 glPopClientAttrib();
1303 }
1304
ProcessTagsBefore(const std::vector<LineData> & line_data,RenderState & render_state,std::size_t begin_line,CPSize begin_char) const1305 void Font::ProcessTagsBefore(const std::vector<LineData>& line_data, RenderState& render_state,
1306 std::size_t begin_line, CPSize begin_char) const
1307 {
1308 double orig_color[4];
1309 glGetDoublev(GL_CURRENT_COLOR, orig_color);
1310
1311 if (line_data.empty())
1312 return;
1313
1314 for (std::size_t i = 0; i <= begin_line; ++i) {
1315 const LineData& line = line_data[i];
1316 for (CPSize j = CP0;
1317 j < ((i == begin_line) ? begin_char : CPSize(line.char_data.size()));
1318 ++j)
1319 {
1320 for (auto& tag : line.char_data[Value(j)].tags) {
1321 HandleTag(tag, orig_color, render_state);
1322 }
1323 }
1324 }
1325 }
1326
StripTags(const std::string & text,bool strip_unpaired_tags)1327 std::string Font::StripTags(const std::string& text, bool strip_unpaired_tags)
1328 {
1329 using namespace boost::xpressive;
1330
1331 sregex & regex = StaticTagHandler().Regex(text, false, strip_unpaired_tags);
1332
1333 mark_tag tag_name_tag(1);
1334 mark_tag open_bracket_tag(2);
1335 mark_tag close_bracket_tag(3);
1336 mark_tag whitespace_tag(4);
1337 mark_tag text_tag(5);
1338
1339 std::stringstream retval;
1340
1341 // scan through matched markup and text, saving only the non-tag-text
1342 sregex_iterator it(text.begin(), text.end(), regex);
1343 sregex_iterator end_it;
1344 for (; it != end_it; ++it) {
1345 sub_match<std::string::const_iterator> const* text_match;
1346 sub_match<std::string::const_iterator> const* whitespace_match;
1347
1348 if ((text_match = &(*it)[text_tag]) && (text_match->matched))
1349 retval << Substring(text, *text_match);
1350
1351 else if ((whitespace_match = &(*it)[whitespace_tag]) && whitespace_match->matched)
1352 retval << Substring(text, *whitespace_match);
1353 }
1354
1355 return retval.str();
1356 }
1357
TextExtent(const std::vector<LineData> & line_data) const1358 Pt Font::TextExtent(const std::vector<LineData>& line_data) const
1359 {
1360 Pt retval;
1361 for (const LineData& line : line_data) {
1362 if (retval.x < line.Width())
1363 retval.x = line.Width();
1364 }
1365 bool is_empty = line_data.empty()
1366 || (line_data.size() == 1 && line_data.front().Empty());
1367 retval.y = is_empty ? Y0 : (static_cast<int>(line_data.size()) - 1) * m_lineskip + m_height;
1368 return retval;
1369 }
1370
RegisterKnownTag(const std::string & tag)1371 void Font::RegisterKnownTag(const std::string& tag)
1372 { StaticTagHandler().Insert(tag); }
1373
RemoveKnownTag(const std::string & tag)1374 void Font::RemoveKnownTag(const std::string& tag)
1375 { StaticTagHandler().Erase(tag); }
1376
ClearKnownTags()1377 void Font::ClearKnownTags()
1378 {
1379 StaticTagHandler().Clear();
1380
1381 // Always know the default tags.
1382 RegisterDefaultTags();
1383 }
1384
ThrowBadGlyph(const std::string & format_str,std::uint32_t c)1385 void Font::ThrowBadGlyph(const std::string& format_str, std::uint32_t c)
1386 {
1387 boost::format format(isprint(c) ? "%c" : "U+%x");
1388 throw BadGlyph(boost::io::str(boost::format(format_str) % boost::io::str(format % c)));
1389 }
1390
1391 namespace DebugOutput {
PrintParseResults(const std::vector<std::shared_ptr<Font::TextElement>> & text_elements)1392 void PrintParseResults(const std::vector<std::shared_ptr<Font::TextElement>>& text_elements) {
1393 std::cout << "results of parse:\n";
1394 for (auto& elem : text_elements) {
1395 if (auto tag_elem = std::dynamic_pointer_cast<Font::FormattingTag>(elem)) {
1396 std::cout << "FormattingTag\n text=\"" << tag_elem->text << "\" (@ "
1397 << static_cast<const void*>(&*tag_elem->text.begin()) << ")\n widths=";
1398 for (const X& width : tag_elem->widths) {
1399 std::cout << width << " ";
1400 }
1401 std::cout << "\n whitespace=" << tag_elem->whitespace << "\n newline=" << tag_elem->newline << "\n params=\n";
1402 for (const Font::Substring& param : tag_elem->params) {
1403 std::cout << " \"" << param << "\"\n";
1404 }
1405 std::cout << " tag_name=\"" << tag_elem->tag_name << "\"\n close_tag=" << tag_elem->close_tag << "\n";
1406 } else {
1407 std::cout << "TextElement\n text=\"" << elem->text << "\" (@ "
1408 << static_cast<const void*>(&*elem->text.begin()) << ")\n widths=";
1409 for (const X& width : elem->widths) {
1410 std::cout << width << " ";
1411 }
1412 std::cout << "\n whitespace=" << elem->whitespace << "\n newline=" << elem->newline << "\n";
1413 }
1414 std::cout << " string_size=" << elem->StringSize() << "\n";
1415 std::cout << "\n";
1416 }
1417 std::cout << std::endl;
1418 }
1419
PrintLineBreakdown(const std::string & text,const Flags<TextFormat> & format,const X box_width,const std::vector<Font::LineData> & line_data)1420 void PrintLineBreakdown(const std::string& text,
1421 const Flags<TextFormat>& format,
1422 const X box_width,
1423 const std::vector<Font::LineData>& line_data)
1424 {
1425 std::cout << "Font::DetermineLines(text=\"" << text << "\" (@ "
1426 << static_cast<const void*>(&*text.begin()) << ") format="
1427 << format << " box_width=" << box_width << ")" << std::endl;
1428
1429 std::cout << "Line breakdown:\n";
1430 for (std::size_t i = 0; i < line_data.size(); ++i) {
1431 std::cout << "Line " << i << ":\n extents=";
1432 for (const auto& character : line_data[i].char_data) {
1433 std::cout << character.extent << " ";
1434 }
1435 std::cout << "\n string indices=";
1436 for (const auto& character : line_data[i].char_data) {
1437 std::cout << character.string_index << " ";
1438 }
1439 std::cout << "\n code point indices=";
1440 for (const auto& character : line_data[i].char_data) {
1441 std::cout << character.code_point_index << " ";
1442 }
1443 std::cout << "\n chars on line: \"";
1444 for (const auto& character : line_data[i].char_data) {
1445 std::cout << text[Value(character.string_index)];
1446 }
1447 std::cout << "\"\n";
1448 for (std::size_t j = 0; j < line_data[i].char_data.size(); ++j) {
1449 for (auto& tag_elem : line_data[i].char_data[j].tags) {
1450 if (tag_elem) {
1451 std::cout << "FormattingTag @" << j << "\n text=\"" << tag_elem->text << "\"\n widths=";
1452 for (const X& width : tag_elem->widths) {
1453 std::cout << width << " ";
1454 }
1455 std::cout << "\n whitespace=" << tag_elem->whitespace
1456 << "\n newline=" << tag_elem->newline << "\n params=\n";
1457 for (const auto& param : tag_elem->params) {
1458 std::cout << " \"" << param << "\"\n";
1459 }
1460 std::cout << " tag_name=\"" << tag_elem->tag_name << "\"\n close_tag="
1461 << tag_elem->close_tag << "\n";
1462 }
1463 }
1464 }
1465 std::cout << " justification=" << line_data[i].justification << "\n" << std::endl;
1466 }
1467 }
1468 }
1469
1470 std::vector<std::shared_ptr<Font::TextElement>>
ExpensiveParseFromTextToTextElements(const std::string & text,const Flags<TextFormat> & format) const1471 Font::ExpensiveParseFromTextToTextElements(const std::string& text,
1472 const Flags<TextFormat>& format) const
1473 {
1474 std::vector<std::shared_ptr<TextElement>> text_elements;
1475
1476 using namespace boost::xpressive;
1477
1478 if (text.empty())
1479 return text_elements;
1480
1481 bool ignore_tags = format & FORMAT_IGNORETAGS;
1482
1483 // Fetch and use the regular expression from the TagHandler which parses all the known XML tags.
1484 sregex& regex = StaticTagHandler().Regex(text, ignore_tags);
1485 sregex_iterator it(text.begin(), text.end(), regex);
1486
1487 // These are the types found by the regular expression: XML open/close tags, text and
1488 // whitespace. Each type will correspond to a type of TextElement.
1489 mark_tag tag_name_tag(1);
1490 mark_tag open_bracket_tag(2);
1491 mark_tag close_bracket_tag(3);
1492 mark_tag whitespace_tag(4);
1493 mark_tag text_tag(5);
1494
1495 sregex_iterator end_it;
1496 while (it != end_it)
1497 {
1498 // Consolidate adjacent blocks of text. If adjacent found substrings are all text, merge
1499 // them into a single Substring.
1500 bool need_increment = true;
1501 Substring combined_text;
1502 sub_match<std::string::const_iterator> const* text_match;
1503 while (it != end_it
1504 && (text_match = &(*it)[text_tag])
1505 && text_match->matched) {
1506 need_increment = false;
1507 if (combined_text.empty())
1508 combined_text = Substring(text, *text_match);
1509 else
1510 combined_text += *text_match;
1511 ++it;
1512 }
1513
1514 // If the element is not a text element then it must be an open tag, a close tag or whitespace.
1515 if (combined_text.empty()) {
1516
1517 // Open XML tag.
1518 if ((*it)[open_bracket_tag].matched) {
1519 auto element = std::make_shared<Font::FormattingTag>(false);
1520 element->text = Substring(text, (*it)[0]);
1521
1522 // Check open tags for submatches which are parameters. For example a Color tag
1523 // might have RGB parameters.
1524 if (1 < (*it).nested_results().size()) {
1525 element->params.reserve((*it).nested_results().size() - 1);
1526 for (auto nested_it = ++(*it).nested_results().begin();
1527 nested_it != (*it).nested_results().end(); ++nested_it)
1528 {
1529 element->params.push_back(Substring(text, (*nested_it)[0]));
1530 }
1531 }
1532 element->tag_name = Substring(text, (*it)[tag_name_tag]);
1533 text_elements.push_back(element);
1534
1535 // Close XML tag
1536 } else if ((*it)[close_bracket_tag].matched) {
1537 auto element = std::make_shared<Font::FormattingTag>(true);
1538 element->text = Substring(text, (*it)[0]);
1539 element->tag_name = Substring(text, (*it)[tag_name_tag]);
1540 text_elements.push_back(element);
1541
1542 // Whitespace element
1543 } else if ((*it)[whitespace_tag].matched) {
1544 auto element = std::make_shared<Font::TextElement>(true, false);
1545 element->text = Substring(text, (*it)[whitespace_tag]);
1546 text_elements.push_back(element);
1547
1548 // If the last character of a whitespace element is a line ending then create a
1549 // newline TextElement.
1550 char last_char = *std::prev(element->text.end());
1551 if (last_char == '\n' || last_char == '\f' || last_char == '\r') {
1552 text_elements.push_back(std::make_shared<Font::TextElement>(false, true));
1553 }
1554 }
1555
1556 // Basic text element.
1557 } else {
1558 auto element = std::make_shared<Font::TextElement>(false, false);
1559 element->text = combined_text;
1560 text_elements.push_back(element);
1561 }
1562
1563 if (need_increment)
1564 ++it;
1565 }
1566
1567 // fill in the widths of code points in each TextElement
1568 FillTemplatedText(text, text_elements, text_elements.begin());
1569
1570 #if DEBUG_DETERMINELINES
1571 DebugOutput::PrintParseResults(text_elements);
1572 #endif
1573 return text_elements;
1574 }
1575
1576
FillTemplatedText(const std::string & text,std::vector<std::shared_ptr<Font::TextElement>> & text_elements,std::vector<std::shared_ptr<Font::TextElement>>::iterator start) const1577 void Font::FillTemplatedText(
1578 const std::string& text,
1579 std::vector<std::shared_ptr<Font::TextElement>>& text_elements,
1580 std::vector<std::shared_ptr<Font::TextElement>>::iterator start) const
1581 {
1582 // For each TextElement in text_elements starting from start.
1583 auto& te_it = start;
1584 for (; te_it != text_elements.end(); ++te_it) {
1585 std::shared_ptr<TextElement> elem = (*te_it);
1586
1587 // For each character in the TextElement.
1588 auto text_it = elem->text.begin();
1589 auto end_it = elem->text.end();
1590 while (text_it != end_it) {
1591
1592 // Find and set the width of the character glyph.
1593 elem->widths.push_back(X0);
1594 std::uint32_t c = utf8::next(text_it, end_it);
1595 if (c != WIDE_NEWLINE) {
1596 auto it = m_glyphs.find(c);
1597 // use a space when an unrendered glyph is requested (the
1598 // space chararacter is always renderable)
1599 elem->widths.back() = (it != m_glyphs.end()) ? it->second.advance : m_space_width;
1600 }
1601 }
1602 }
1603 }
1604
ChangeTemplatedText(std::string & text,std::vector<std::shared_ptr<Font::TextElement>> & text_elements,const std::string & new_text,size_t targ_offset) const1605 void Font::ChangeTemplatedText(
1606 std::string& text,
1607 std::vector<std::shared_ptr<Font::TextElement>>& text_elements,
1608 const std::string& new_text,
1609 size_t targ_offset) const
1610 {
1611 if (targ_offset >= text_elements.size())
1612 return;
1613
1614 if (new_text.empty())
1615 return;
1616
1617 int change_of_len = 0;
1618
1619 // Find the target text element.
1620 size_t curr_offset = 0;
1621 std::vector<std::shared_ptr<Font::TextElement>>::iterator te_it = text_elements.begin();
1622 while (te_it != text_elements.end()) {
1623 if ((*te_it)->Type() == TextElement::TEXT) {
1624 // Change the target text element
1625 if (targ_offset == curr_offset) {
1626 // Change text
1627 size_t ii_sub_begin = (*te_it)->text.begin() - text.begin();
1628 size_t sub_len = (*te_it)->text.end() - (*te_it)->text.begin();
1629 text.erase(ii_sub_begin, sub_len);
1630 text.insert(ii_sub_begin, new_text);
1631
1632 change_of_len = new_text.size() - sub_len;
1633 (*te_it)->text = Substring(text,
1634 std::next(text.begin(), ii_sub_begin),
1635 std::next(text.begin(), ii_sub_begin + new_text.size()));
1636 break;
1637 }
1638 ++curr_offset;
1639 }
1640 ++te_it;
1641 }
1642
1643 if (te_it == text_elements.end())
1644 return;
1645
1646 std::vector<std::shared_ptr<Font::TextElement>>::iterator start_of_reflow = te_it;
1647
1648 if (change_of_len != 0) {
1649 ++te_it;
1650 // Adjust the offset of each subsequent text_element
1651 while (te_it != text_elements.end())
1652 {
1653 size_t ii_sub_begin = (*te_it)->text.begin() - text.begin();
1654 size_t ii_sub_end = (*te_it)->text.end() - text.begin();
1655 (*te_it)->text = Substring(text,
1656 std::next(text.begin(), ii_sub_begin + change_of_len),
1657 std::next(text.begin(), ii_sub_end + change_of_len));
1658
1659 ++te_it;
1660 }
1661 }
1662
1663 FillTemplatedText(text, text_elements, start_of_reflow);
1664 }
1665
DetermineLines(const std::string & text,Flags<TextFormat> & format,X box_width,const std::vector<std::shared_ptr<TextElement>> & text_elements) const1666 std::vector<Font::LineData> Font::DetermineLines(
1667 const std::string& text, Flags<TextFormat>& format, X box_width,
1668 const std::vector<std::shared_ptr<TextElement>>& text_elements) const
1669 {
1670 // HACK - Workaround for #2166
1671 // On OSX, right clicking an unowned planet at game start may result in utf8::invalid_utf8 or utf8::not_enough_room
1672 if (!utf8::is_valid(text.begin(), text.end())) {
1673 std::cerr << "Invalid UTF8 in text: " << text;
1674 return std::vector<Font::LineData>{};
1675 }
1676
1677 ValidateFormat(format);
1678
1679 RenderState render_state;
1680 int tab_width = 8; // default tab width
1681 X tab_pixel_width = tab_width * m_space_width; // get the length of a tab stop
1682 bool expand_tabs = format & FORMAT_LEFT; // tab expansion only takes place when the lines are left-justified (otherwise, tabs are just spaces)
1683 Alignment orig_just = ALIGN_NONE;
1684 if (format & FORMAT_LEFT)
1685 orig_just = ALIGN_LEFT;
1686 if (format & FORMAT_CENTER)
1687 orig_just = ALIGN_CENTER;
1688 if (format & FORMAT_RIGHT)
1689 orig_just = ALIGN_RIGHT;
1690 bool last_line_of_curr_just = false; // is this the last line of the current justification? (for instance when a </right> tag is encountered)
1691
1692 std::vector<Font::LineData> line_data;
1693 if (!text_elements.empty()) {
1694 line_data.push_back(LineData());
1695 line_data.back().justification = orig_just;
1696 }
1697
1698 X x = X0;
1699 // the position within the original string of the current TextElement
1700 StrSize original_string_offset(0);
1701 // the index of the first code point of the current TextElement
1702 CPSize code_point_offset(0);
1703 std::vector<std::shared_ptr<TextElement>> pending_formatting_tags;
1704 for (const auto& elem : text_elements) {
1705 // if a newline is explicitly requested, start a new one
1706 if (elem->Type() == TextElement::NEWLINE) {
1707 line_data.push_back(LineData());
1708 SetJustification(last_line_of_curr_just,
1709 line_data.back(),
1710 orig_just,
1711 line_data[line_data.size() - 2].justification);
1712 x = X0;
1713
1714 } else if (elem->Type() == TextElement::WHITESPACE) {
1715 auto it = elem->text.begin();
1716 auto end_it = elem->text.end();
1717 while (it != end_it) {
1718 StrSize char_index(std::distance(elem->text.begin(), it));
1719 std::uint32_t c = utf8::next(it, end_it);
1720 StrSize char_size = std::distance(elem->text.begin(), it) - char_index;
1721 if (c != WIDE_CR && c != WIDE_FF) {
1722 X advance_position = x + m_space_width;
1723 if (c == WIDE_TAB && expand_tabs)
1724 advance_position = (((x / tab_pixel_width) + 1) * tab_pixel_width);
1725 else if (c == WIDE_NEWLINE)
1726 advance_position = x;
1727 X advance = advance_position - x;
1728 // if we're using linewrap and this space won't fit on
1729 // this line
1730 if ((format & FORMAT_LINEWRAP) && box_width < advance_position) {
1731 if (!x && box_width < advance) {
1732 // if the space is larger than the line and alone
1733 // on the line, let the space overrun this line
1734 // and then start a new one
1735 line_data.push_back(LineData());
1736 x = X0; // reset the x-position to 0
1737 SetJustification(last_line_of_curr_just,
1738 line_data.back(),
1739 orig_just,
1740 line_data[line_data.size() - 2].justification);
1741 } else {
1742 // otherwise start a new line and put the space there
1743 line_data.push_back(LineData());
1744 x = advance;
1745 line_data.back().char_data.push_back(
1746 LineData::CharData(x,
1747 original_string_offset + char_index,
1748 char_size,
1749 code_point_offset,
1750 pending_formatting_tags));
1751 pending_formatting_tags.clear();
1752 SetJustification(last_line_of_curr_just,
1753 line_data.back(),
1754 orig_just,
1755 line_data[line_data.size() - 2].justification);
1756 }
1757 } else { // there's room for the space, or we're not using linewrap
1758 x += advance;
1759 line_data.back().char_data.push_back(
1760 LineData::CharData(x,
1761 original_string_offset + char_index,
1762 char_size,
1763 code_point_offset,
1764 pending_formatting_tags));
1765 pending_formatting_tags.clear();
1766 }
1767 }
1768 ++code_point_offset;
1769 }
1770 } else if (elem->Type() == TextElement::TEXT) {
1771 if (format & FORMAT_WORDBREAK) {
1772 // if the text "word" overruns this line, and isn't alone on
1773 // this line, move it down to the next line
1774 if (box_width < x + elem->Width() && x) {
1775 line_data.push_back(LineData());
1776 x = X0;
1777 SetJustification(last_line_of_curr_just,
1778 line_data.back(),
1779 orig_just,
1780 line_data[line_data.size() - 2].justification);
1781 }
1782 auto it = elem->text.begin();
1783 auto end_it = elem->text.end();
1784 std::size_t j = 0;
1785 while (it != end_it) {
1786 StrSize char_index(std::distance(elem->text.begin(), it));
1787 utf8::next(it, end_it);
1788 StrSize char_size = std::distance(elem->text.begin(), it) - char_index;
1789 x += elem->widths[j];
1790 line_data.back().char_data.push_back(
1791 LineData::CharData(x,
1792 original_string_offset + char_index,
1793 char_size,
1794 code_point_offset,
1795 pending_formatting_tags));
1796 pending_formatting_tags.clear();
1797 ++j;
1798 ++code_point_offset;
1799 }
1800 } else {
1801 auto it = elem->text.begin();
1802 auto end_it = elem->text.end();
1803 std::size_t j = 0;
1804 while (it != end_it) {
1805 StrSize char_index(std::distance(elem->text.begin(), it));
1806 utf8::next(it, end_it);
1807 StrSize char_size = std::distance(elem->text.begin(), it) - char_index;
1808 // if the char overruns this line, and isn't alone on this
1809 // line, move it down to the next line
1810 if ((format & FORMAT_LINEWRAP) && box_width < x + elem->widths[j] && x) {
1811 line_data.push_back(LineData());
1812 x = elem->widths[j];
1813 line_data.back().char_data.push_back(
1814 LineData::CharData(x,
1815 original_string_offset + char_index,
1816 char_size,
1817 code_point_offset,
1818 pending_formatting_tags));
1819 pending_formatting_tags.clear();
1820 SetJustification(last_line_of_curr_just,
1821 line_data.back(),
1822 orig_just,
1823 line_data[line_data.size() - 2].justification);
1824 } else {
1825 // there's room for this char on this line, or there's
1826 // no wrapping in use
1827 x += elem->widths[j];
1828 line_data.back().char_data.push_back(
1829 LineData::CharData(x,
1830 original_string_offset + char_index,
1831 char_size,
1832 code_point_offset,
1833 pending_formatting_tags));
1834 pending_formatting_tags.clear();
1835 }
1836 ++j;
1837 ++code_point_offset;
1838 }
1839 }
1840 } else if (elem->Type() == TextElement::OPEN_TAG) {
1841 assert(std::dynamic_pointer_cast<FormattingTag>(elem));
1842 std::shared_ptr<FormattingTag> elem_as_tag =
1843 std::static_pointer_cast<FormattingTag>(elem);
1844 if (elem_as_tag->tag_name == ALIGN_LEFT_TAG)
1845 line_data.back().justification = ALIGN_LEFT;
1846 else if (elem_as_tag->tag_name == ALIGN_CENTER_TAG)
1847 line_data.back().justification = ALIGN_CENTER;
1848 else if (elem_as_tag->tag_name == ALIGN_RIGHT_TAG)
1849 line_data.back().justification = ALIGN_RIGHT;
1850 else if (elem_as_tag->tag_name != PRE_TAG)
1851 pending_formatting_tags.push_back(elem);
1852 last_line_of_curr_just = false;
1853 code_point_offset += elem->CodePointSize();
1854 } else if (elem->Type() == TextElement::CLOSE_TAG) {
1855 assert(std::dynamic_pointer_cast<FormattingTag>(elem));
1856 std::shared_ptr<FormattingTag> elem_as_tag =
1857 std::static_pointer_cast<FormattingTag>(elem);
1858 if ((elem_as_tag->tag_name == ALIGN_LEFT_TAG && line_data.back().justification == ALIGN_LEFT) ||
1859 (elem_as_tag->tag_name == ALIGN_CENTER_TAG && line_data.back().justification == ALIGN_CENTER) ||
1860 (elem_as_tag->tag_name == ALIGN_RIGHT_TAG && line_data.back().justification == ALIGN_RIGHT))
1861 last_line_of_curr_just = true;
1862 else if (elem_as_tag->tag_name != PRE_TAG)
1863 pending_formatting_tags.push_back(elem);
1864 code_point_offset += elem->CodePointSize();
1865 }
1866 original_string_offset += elem->StringSize();
1867 }
1868 // disregard the final pending formatting tag, if any, since this is the
1869 // end of the text, and so it cannot have any effect
1870
1871 #if DEBUG_DETERMINELINES
1872 DebugOutput::PrintLineBreakdown(text, format, box_width, line_data);
1873 #endif
1874
1875 return line_data;
1876 }
1877
GetFace(FT_Face & face)1878 FT_Error Font::GetFace(FT_Face& face)
1879 { return FT_New_Face(g_library.m_library, m_font_filename.c_str(), 0, &face); }
1880
GetFace(const std::vector<unsigned char> & file_contents,FT_Face & face)1881 FT_Error Font::GetFace(const std::vector<unsigned char>& file_contents, FT_Face& face)
1882 {
1883 return FT_New_Memory_Face(g_library.m_library, &file_contents[0],
1884 file_contents.size(), 0, &face);
1885 }
1886
CheckFace(FT_Face face,FT_Error error)1887 void Font::CheckFace(FT_Face face, FT_Error error)
1888 {
1889 if (error || !face)
1890 throw BadFile("Face object created from \"" + m_font_filename + "\" was invalid");
1891 if (!FT_IS_SCALABLE(face)) {
1892 throw UnscalableFont("Attempted to create font \"" + m_font_filename +
1893 "\" with uscalable font face");
1894 }
1895 }
1896
Init(FT_Face & face)1897 void Font::Init(FT_Face& face)
1898 {
1899 FT_Fixed scale;
1900
1901 if (!m_pt_sz) {
1902 throw InvalidPointSize("Attempted to create font \"" + m_font_filename +
1903 "\" with 0 point size");
1904 }
1905
1906 // Set the character size and use default 72 DPI
1907 if (FT_Set_Char_Size(face, 0, m_pt_sz * 64, 0, 0)) // if error is returned
1908 throw BadPointSize("Could not set font size while attempting to create font \"" + m_font_filename + "\"");
1909
1910 // Get the scalable font metrics for this font
1911 scale = face->size->metrics.y_scale;
1912 m_ascent = Y(static_cast<int>(face->size->metrics.ascender / 64.0)); // convert from fixed-point 26.6 format
1913 m_descent = Y(static_cast<int>(face->size->metrics.descender / 64.0)); // convert from fixed-point 26.6 format
1914 m_height = m_ascent - m_descent + 1;
1915 m_lineskip = Y(static_cast<int>(face->size->metrics.height / 64.0));
1916 // underline info
1917 m_underline_offset = std::floor(FT_MulFix(face->underline_position, scale) / 64.0);
1918 m_underline_height = std::ceil(FT_MulFix(face->underline_thickness, scale) / 64.0);
1919 if (m_underline_height < 1.0) {
1920 m_underline_height = 1.0;
1921 }
1922 // italics info
1923 m_italics_offset = Value(ITALICS_FACTOR * m_height / 2.0);
1924 // shadow info
1925 m_shadow_offset = 1.0;
1926 // super/subscript
1927 m_super_sub_offset = Value(m_height / 4.0);
1928
1929 // we always need these whitespace, number, and punctuation characters
1930 std::vector<std::pair<std::uint32_t, std::uint32_t>> range_vec(
1931 PRINTABLE_ASCII_NONALPHA_RANGES.begin(),
1932 PRINTABLE_ASCII_NONALPHA_RANGES.end());
1933
1934 // add ASCII letter characters or user-specified scripts to them, if the user specifies a specific set of
1935 // characters
1936 if (m_charsets.empty()) {
1937 range_vec.insert(range_vec.end(),
1938 PRINTABLE_ASCII_ALPHA_RANGES.begin(),
1939 PRINTABLE_ASCII_ALPHA_RANGES.end());
1940 } else {
1941 typedef std::pair<std::uint32_t, std::uint32_t> Pair;
1942 for (std::size_t i = 0; i < m_charsets.size(); ++i) {
1943 range_vec.push_back(Pair(m_charsets[i].m_first_char, m_charsets[i].m_last_char));
1944 }
1945 }
1946
1947 //Get maximum texture size
1948 GLint GL_TEX_MAX_SIZE;
1949 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &GL_TEX_MAX_SIZE);
1950 const std::size_t TEX_MAX_SIZE = GL_TEX_MAX_SIZE;
1951
1952 std::map<std::uint32_t, TempGlyphData> temp_glyph_data;
1953
1954 // Start with width and height of 16,
1955 // increase them as needed.
1956 // We will lay out the glyphs on the texture side by side
1957 // until the width would reach TEX_MAX_SIZE, then go to the next row.
1958 // QUESTION: Would a more square-like shape be better for the texture?
1959 Buffer2d<std::uint16_t> buffer(X(16), Y(16), 0);
1960
1961 X x = X0;
1962 Y y = Y0;
1963 X max_x = X0;
1964 Y max_y = Y0;
1965 for (const auto& range : range_vec) {
1966 std::uint32_t low = range.first;
1967 std::uint32_t high = range.second;
1968
1969 // copy glyph images
1970 for (std::uint32_t c = low; c < high; ++c) {
1971 if (!temp_glyph_data.count(c) && GenerateGlyph(face, c)) {
1972 const FT_Bitmap& glyph_bitmap = face->glyph->bitmap;
1973 if ((glyph_bitmap.width > TEX_MAX_SIZE) | (glyph_bitmap.rows > TEX_MAX_SIZE)) {
1974 ThrowBadGlyph("GG::Font::Init : Glyph too large for buffer'%1%'", c); // catch broken fonts
1975 }
1976
1977 if (Value(x) + glyph_bitmap.width >= TEX_MAX_SIZE) { // start a new row of glyph images
1978 if (x > max_x) max_x = x;
1979 x = X0;
1980 y = max_y;
1981 }
1982 if (Value(y) + glyph_bitmap.rows >= TEX_MAX_SIZE) {
1983 // We cannot make the texture any larger. The font does not fit.
1984 ThrowBadGlyph("GG::Font::Init : Face too large for buffer. First glyph to no longer fit: '%1%'", c);
1985 }
1986 if (y + Y(glyph_bitmap.rows) > max_y) {
1987 max_y = y + Y(glyph_bitmap.rows + 1); //Leave a one pixel gap between glyphs
1988 }
1989
1990 std::uint8_t* src_start = glyph_bitmap.buffer;
1991 // Resize buffer to fit new data
1992 buffer.at(x + X(glyph_bitmap.width), y + Y(glyph_bitmap.rows)) = 0;
1993
1994 for (unsigned int row = 0; row < glyph_bitmap.rows; ++row) {
1995 std::uint8_t* src = src_start + row * glyph_bitmap.pitch;
1996 std::uint16_t* dst = &buffer.get(x, y + Y(row));
1997 // Rows are always contiguous, so we can copy along a row using simple incrementation
1998 for (unsigned int col = 0; col < glyph_bitmap.width; ++col) {
1999 #ifdef __BIG_ENDIAN__
2000 *dst++ = *src++ | (255 << 8); // big-endian uses different byte ordering
2001 #else
2002 *dst++ = (*src++ << 8) | 255; // alpha is the value from glyph_bitmap; luminance is always 100% white
2003 #endif
2004 }
2005 }
2006
2007 // record info on how to find and use this glyph later
2008 temp_glyph_data[c] =
2009 TempGlyphData(Pt(x, y), Pt(x + X(glyph_bitmap.width), y + Y(glyph_bitmap.rows)),
2010 Y(m_height - 1 + m_descent - face->glyph->bitmap_top),
2011 X(static_cast<int>((std::ceil(face->glyph->metrics.horiBearingX / 64.0)))),
2012 X(static_cast<int>((std::ceil(face->glyph->metrics.horiAdvance / 64.0)))));
2013
2014 // advance buffer write-position
2015 x += X(glyph_bitmap.width + 1); //Leave a one pixel gap between glyphs
2016 }
2017 }
2018 }
2019
2020 buffer.MakePowerOfTwo();
2021
2022 // create opengl texture from buffer
2023 m_texture.reset(new Texture);
2024 m_texture->Init(buffer.BufferWidth(), buffer.BufferHeight(),
2025 (unsigned char*)buffer.Buffer(), GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 2);
2026
2027 // create Glyph objects from temp glyph data
2028 for (const auto& glyph_data : temp_glyph_data)
2029 m_glyphs[glyph_data.first] = Glyph(m_texture, glyph_data.second.ul, glyph_data.second.lr, glyph_data.second.y_offset, glyph_data.second.left_b, glyph_data.second.adv);
2030
2031 // record the width of the space character
2032 auto glyph_it = m_glyphs.find(WIDE_SPACE);
2033 assert(glyph_it != m_glyphs.end());
2034 m_space_width = glyph_it->second.advance;
2035 }
2036
GenerateGlyph(FT_Face face,std::uint32_t ch)2037 bool Font::GenerateGlyph(FT_Face face, std::uint32_t ch)
2038 {
2039 bool retval = true;
2040
2041 // load the glyph
2042 if (!face)
2043 throw BadFace("GG::Font::GetGlyphBitmap : invalid font or font face");
2044
2045 FT_UInt index = FT_Get_Char_Index(face, ch);
2046 if (index) {
2047 if (FT_Load_Glyph(face, index, FT_LOAD_DEFAULT)) {
2048 // loading of a glpyh failed so we replace it with
2049 // the 'Replacement Character' at codepoint 0xFFFD
2050 FT_UInt tmp_index = FT_Get_Char_Index(face, 0xFFFD);
2051 if (FT_Load_Glyph(face, tmp_index, FT_LOAD_DEFAULT))
2052 ThrowBadGlyph("GG::Font::GetGlyphBitmap : Freetype could not load the glyph for character '%1%'", ch);
2053 }
2054
2055 FT_GlyphSlot glyph = face->glyph;
2056
2057 // render the glyph
2058 if (FT_Render_Glyph(glyph, ft_render_mode_normal))
2059 ThrowBadGlyph("GG::Font::GetGlyphBitmap : Freetype could not render the glyph for character '%1%'", ch);
2060 } else {
2061 retval = false;
2062 }
2063
2064 return retval;
2065 }
2066
ValidateFormat(Flags<TextFormat> & format) const2067 void Font::ValidateFormat(Flags<TextFormat>& format) const
2068 {
2069 // correct any disagreements in the format flags
2070 int dup_ct = 0; // duplication count
2071 if (format & FORMAT_LEFT) ++dup_ct;
2072 if (format & FORMAT_RIGHT) ++dup_ct;
2073 if (format & FORMAT_CENTER) ++dup_ct;
2074 if (dup_ct != 1) { // exactly one must be picked; when none or multiples are picked, use FORMAT_LEFT by default
2075 format &= ~(FORMAT_RIGHT | FORMAT_CENTER);
2076 format |= FORMAT_LEFT;
2077 }
2078 dup_ct = 0;
2079 if (format & FORMAT_TOP) ++dup_ct;
2080 if (format & FORMAT_BOTTOM) ++dup_ct;
2081 if (format & FORMAT_VCENTER) ++dup_ct;
2082 if (dup_ct != 1) { // exactly one must be picked; when none or multiples are picked, use FORMAT_TOP by default
2083 format &= ~(FORMAT_BOTTOM | FORMAT_VCENTER);
2084 format |= FORMAT_TOP;
2085 }
2086 if ((format & FORMAT_WORDBREAK) && (format & FORMAT_LINEWRAP)) // only one of these can be picked; FORMAT_WORDBREAK overrides FORMAT_LINEWRAP
2087 format &= ~FORMAT_LINEWRAP;
2088 }
2089
StoreGlyphImpl(Font::RenderCache & cache,Clr color,const Pt & pt,const Glyph & glyph,int x_top_offset,int y_shift) const2090 void Font::StoreGlyphImpl(Font::RenderCache& cache, Clr color, const Pt& pt,
2091 const Glyph& glyph, int x_top_offset, int y_shift) const
2092 {
2093 cache.coordinates->store(glyph.sub_texture.TexCoords()[0], glyph.sub_texture.TexCoords()[1]);
2094 cache.vertices->store(pt.x + glyph.left_bearing + x_top_offset, pt.y + glyph.y_offset + y_shift);
2095 cache.colors->store(color);
2096
2097 cache.coordinates->store(glyph.sub_texture.TexCoords()[2], glyph.sub_texture.TexCoords()[1]);
2098 cache.vertices->store(pt.x + glyph.sub_texture.Width() + glyph.left_bearing + x_top_offset,
2099 pt.y + glyph.y_offset + y_shift);
2100 cache.colors->store(color);
2101
2102 cache.coordinates->store(glyph.sub_texture.TexCoords()[2], glyph.sub_texture.TexCoords()[3]);
2103 cache.vertices->store(pt.x + glyph.sub_texture.Width() + glyph.left_bearing - x_top_offset,
2104 pt.y + glyph.sub_texture.Height() + glyph.y_offset + y_shift);
2105 cache.colors->store(color);
2106
2107 cache.coordinates->store(glyph.sub_texture.TexCoords()[0], glyph.sub_texture.TexCoords()[3]);
2108 cache.vertices->store(pt.x + glyph.left_bearing - x_top_offset,
2109 pt.y + glyph.sub_texture.Height() + glyph.y_offset + y_shift);
2110 cache.colors->store(color);
2111 }
2112
StoreUnderlineImpl(Font::RenderCache & cache,Clr color,const Pt & pt,const Glyph & glyph,Y descent,Y height,Y underline_height,Y underline_offset) const2113 void Font::StoreUnderlineImpl(Font::RenderCache& cache, Clr color, const Pt& pt, const Glyph& glyph,
2114 Y descent, Y height, Y underline_height, Y underline_offset) const
2115 {
2116 X x1 = pt.x;
2117 Y y1(pt.y + height + descent - underline_offset);
2118 X x2 = x1 + glyph.advance;
2119 Y y2(y1 + underline_height);
2120
2121 cache.underline_vertices->store(x1, y1);
2122 cache.underline_colors->store(color);
2123 cache.underline_vertices->store(x2, y1);
2124 cache.underline_colors->store(color);
2125 cache.underline_vertices->store(x2, y2);
2126 cache.underline_colors->store(color);
2127 cache.underline_vertices->store(x1, y2);
2128 cache.underline_colors->store(color);
2129 }
2130
StoreGlyph(const Pt & pt,const Glyph & glyph,const Font::RenderState * render_state,Font::RenderCache & cache) const2131 X Font::StoreGlyph(const Pt& pt, const Glyph& glyph, const Font::RenderState* render_state,
2132 Font::RenderCache& cache) const
2133 {
2134 int italic_top_offset = 0;
2135 int shadow_offset = 0;
2136 int super_sub_offset = 0;
2137
2138 if (render_state && render_state->use_italics) {
2139 // Should we enable sub pixel italics offsets?
2140 italic_top_offset = static_cast<int>(m_italics_offset);
2141 }
2142 if (render_state && render_state->use_shadow) {
2143 shadow_offset = static_cast<int>(m_shadow_offset);
2144 }
2145 if (render_state) {
2146 super_sub_offset = -static_cast<int>(render_state->super_sub_shift * m_super_sub_offset);
2147 }
2148
2149 // render shadows?
2150 if (shadow_offset > 0) {
2151 StoreGlyphImpl(cache, CLR_BLACK, pt + Pt(X1, Y0), glyph, italic_top_offset, super_sub_offset);
2152 StoreGlyphImpl(cache, CLR_BLACK, pt + Pt(X0, Y1), glyph, italic_top_offset, super_sub_offset);
2153 StoreGlyphImpl(cache, CLR_BLACK, pt + Pt(-X1, Y0), glyph, italic_top_offset, super_sub_offset);
2154 StoreGlyphImpl(cache, CLR_BLACK, pt + Pt(X0, -Y1), glyph, italic_top_offset, super_sub_offset);
2155 if (render_state && render_state->draw_underline) {
2156 StoreUnderlineImpl(cache, CLR_BLACK, pt + Pt(X0, Y1), glyph, m_descent,
2157 m_height, Y(m_underline_height), Y(m_underline_offset));
2158 StoreUnderlineImpl(cache, CLR_BLACK, pt + Pt(X0, -Y1), glyph, m_descent,
2159 m_height, Y(m_underline_height), Y(m_underline_offset));
2160 }
2161 }
2162
2163 // render main text
2164 if (render_state) {
2165 StoreGlyphImpl(cache, render_state->CurrentColor(), pt, glyph, italic_top_offset, super_sub_offset);
2166 if (render_state->draw_underline) {
2167 StoreUnderlineImpl(cache, render_state->CurrentColor(), pt, glyph, m_descent,
2168 m_height, Y(m_underline_height), Y(m_underline_offset));
2169 }
2170 }
2171
2172 return glyph.advance;
2173 }
2174
HandleTag(const std::shared_ptr<FormattingTag> & tag,double * orig_color,RenderState & render_state) const2175 void Font::HandleTag(const std::shared_ptr<FormattingTag>& tag, double* orig_color,
2176 RenderState& render_state) const
2177 {
2178 if (tag->tag_name == ITALIC_TAG) {
2179 if (tag->close_tag) {
2180 if (render_state.use_italics) {
2181 --render_state.use_italics;
2182 }
2183 } else {
2184 ++render_state.use_italics;
2185 }
2186 } else if (tag->tag_name == UNDERLINE_TAG) {
2187 if (tag->close_tag) {
2188 if (render_state.draw_underline) {
2189 --render_state.draw_underline;
2190 }
2191 } else {
2192 ++render_state.draw_underline;
2193 }
2194 } else if (tag->tag_name == SHADOW_TAG) {
2195 if (tag->close_tag) {
2196 if (render_state.use_shadow) {
2197 --render_state.use_shadow;
2198 }
2199 } else {
2200 ++render_state.use_shadow;
2201 }
2202 } else if (tag->tag_name == SUPERSCRIPT_TAG) {
2203 if (tag->close_tag) {
2204 --render_state.super_sub_shift;
2205 } else {
2206 ++render_state.super_sub_shift;
2207 }
2208 } else if (tag->tag_name == SUBSCRIPT_TAG) {
2209 if (tag->close_tag) {
2210 ++render_state.super_sub_shift;
2211 } else {
2212 --render_state.super_sub_shift;
2213 }
2214 } else if (tag->tag_name == RGBA_TAG) {
2215 if (tag->close_tag) {
2216 // Popping is ok also for an empty color stack.
2217 render_state.PopColor();
2218 } else {
2219 using boost::lexical_cast;
2220 bool well_formed_tag = true;
2221 if (4 == tag->params.size()) {
2222 try {
2223 int temp_color[4];
2224 GLubyte color[4];
2225 temp_color[0] = lexical_cast<int>(tag->params[0]);
2226 temp_color[1] = lexical_cast<int>(tag->params[1]);
2227 temp_color[2] = lexical_cast<int>(tag->params[2]);
2228 temp_color[3] = lexical_cast<int>(tag->params[3]);
2229 if (0 <= temp_color[0] && temp_color[0] <= 255 &&
2230 0 <= temp_color[1] && temp_color[1] <= 255 &&
2231 0 <= temp_color[2] && temp_color[2] <= 255 &&
2232 0 <= temp_color[3] && temp_color[3] <= 255)
2233 {
2234 color[0] = temp_color[0];
2235 color[1] = temp_color[1];
2236 color[2] = temp_color[2];
2237 color[3] = temp_color[3];
2238 glColor4ubv(color);
2239 render_state.PushColor(color[0], color[1], color[2], color[3]);
2240 } else {
2241 well_formed_tag = false;
2242 }
2243 } catch (const boost::bad_lexical_cast&) {
2244 try {
2245 double color[4];
2246 color[0] = lexical_cast<double>(tag->params[0]);
2247 color[1] = lexical_cast<double>(tag->params[1]);
2248 color[2] = lexical_cast<double>(tag->params[2]);
2249 color[3] = lexical_cast<double>(tag->params[3]);
2250 if (0.0 <= color[0] && color[0] <= 1.0 &&
2251 0.0 <= color[1] && color[1] <= 1.0 &&
2252 0.0 <= color[2] && color[2] <= 1.0 &&
2253 0.0 <= color[3] && color[3] <= 1.0)
2254 {
2255 glColor4dv(color);
2256 render_state.PushColor(color[0], color[1], color[2], color[3]);
2257 } else {
2258 well_formed_tag = false;
2259 }
2260 } catch (const boost::bad_lexical_cast&) {
2261 well_formed_tag = false;
2262 }
2263 }
2264 } else {
2265 well_formed_tag = false;
2266 }
2267 if (!well_formed_tag) {
2268 std::cerr << "GG::Font : Encountered malformed <rgba> formatting tag: "
2269 << tag->text;
2270 }
2271 }
2272 }
2273 }
2274
IsDefaultFont()2275 bool Font::IsDefaultFont()
2276 { return m_font_filename == StyleFactory::DefaultFontName(); }
2277
GetDefaultFont(unsigned int pts)2278 std::shared_ptr<Font> Font::GetDefaultFont(unsigned int pts)
2279 { return GUI::GetGUI()->GetStyleFactory()->DefaultFont(pts); }
2280
2281
2282 ///////////////////////////////////////
2283 // class GG::FontManager
2284 ///////////////////////////////////////
2285 // FontKey
FontKey(const std::string & str,unsigned int pts)2286 FontManager::FontKey::FontKey(const std::string& str, unsigned int pts) :
2287 filename(str),
2288 points(pts)
2289 {}
2290
operator <(const FontKey & rhs) const2291 bool FontManager::FontKey::operator<(const FontKey& rhs) const
2292 { return (filename < rhs.filename || (filename == rhs.filename && points < rhs.points)); }
2293
2294 // FontManager
2295 // static(s)
2296 const std::shared_ptr<Font> FontManager::EMPTY_FONT{std::make_shared<Font>("", 0)};
2297
FontManager()2298 FontManager::FontManager()
2299 {}
2300
HasFont(const std::string & font_filename,unsigned int pts) const2301 bool FontManager::HasFont(const std::string& font_filename, unsigned int pts) const
2302 { return m_rendered_fonts.count(FontKey(font_filename, pts)); }
2303
GetFont(const std::string & font_filename,unsigned int pts)2304 std::shared_ptr<Font> FontManager::GetFont(const std::string& font_filename, unsigned int pts)
2305 {
2306 std::vector<UnicodeCharset> v;
2307 std::vector<UnicodeCharset>::iterator it = v.end();
2308 return GetFont(font_filename, pts, it, it);
2309 }
2310
GetFont(const std::string & font_filename,unsigned int pts,const std::vector<unsigned char> & file_contents)2311 std::shared_ptr<Font> FontManager::GetFont(const std::string& font_filename, unsigned int pts,
2312 const std::vector<unsigned char>& file_contents)
2313 {
2314 std::vector<UnicodeCharset> v;
2315 std::vector<UnicodeCharset>::iterator it = v.end();
2316 return GetFont(font_filename, pts, file_contents, it, it);
2317 }
2318
FreeFont(const std::string & font_filename,unsigned int pts)2319 void FontManager::FreeFont(const std::string& font_filename, unsigned int pts)
2320 {
2321 FontKey key(font_filename, pts);
2322 std::map<FontKey, std::shared_ptr<Font>>::iterator it = m_rendered_fonts.find(key);
2323 if (it != m_rendered_fonts.end())
2324 m_rendered_fonts.erase(it);
2325 }
2326
GetFontManager()2327 FontManager& GG::GetFontManager()
2328 {
2329 static FontManager manager;
2330 return manager;
2331 }
2332