# gfx_layout.cpp

Go to the documentation of this file.

    00001 /* $Id: gfx_layout.cpp 26037 2013-11-18 19:47:43Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "gfx_layout.h"
    00014 #include "string_func.h"
    00015 #include "strings_func.h"
    00016 #include "debug.h"
    00017
    00018 #include "table/control_codes.h"
    00019
    00020 #ifdef WITH_ICU
    00021 #include <unicode/ustring.h>
    00022 #endif /* WITH_ICU */
    00023
    00024
    00026 Layouter::LineCache *Layouter::linecache;
    00027
    00029 Layouter::FontColourMap Layouter::fonts[FS_END];
    00030
    00031
    00037 Font::Font(FontSize size, TextColour colour) :
    00038     fc(FontCache::Get(size)), colour(colour)
    00039 {
    00040   assert(size < FS_END);
    00041 }
    00042
    00043 #ifdef WITH_ICU
    00044 /* Implementation details of LEFontInstance */
    00045
    00046 le_int32 Font::getUnitsPerEM() const
    00047 {
    00048   return this->fc->GetUnitsPerEM();
    00049 }
    00050
    00051 le_int32 Font::getAscent() const
    00052 {
    00053   return this->fc->GetAscender();
    00054 }
    00055
    00056 le_int32 Font::getDescent() const
    00057 {
    00058   return -this->fc->GetDescender();
    00059 }
    00060
    00061 le_int32 Font::getLeading() const
    00062 {
    00063   return this->fc->GetHeight();
    00064 }
    00065
    00066 float Font::getXPixelsPerEm() const
    00067 {
    00068   return (float)this->fc->GetHeight();
    00069 }
    00070
    00071 float Font::getYPixelsPerEm() const
    00072 {
    00073   return (float)this->fc->GetHeight();
    00074 }
    00075
    00076 float Font::getScaleFactorX() const
    00077 {
    00078   return 1.0f;
    00079 }
    00080
    00081 float Font::getScaleFactorY() const
    00082 {
    00083   return 1.0f;
    00084 }
    00085
    00086 const void *Font::getFontTable(LETag tableTag) const
    00087 {
    00088   size_t length;
    00089   return this->getFontTable(tableTag, length);
    00090 }
    00091
    00092 const void *Font::getFontTable(LETag tableTag, size_t &length) const
    00093 {
    00094   return this->fc->GetFontTable(tableTag, length);
    00095 }
    00096
    00097 LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const
    00098 {
    00099   if (IsTextDirectionChar(ch)) return 0;
    00100   return this->fc->MapCharToGlyph(ch);
    00101 }
    00102
    00103 void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const
    00104 {
    00105   advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph);
    00106   advance.fY = 0;
    00107 }
    00108
    00109 le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const
    00110 {
    00111   return FALSE;
    00112 }
    00113
    00114 static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
    00115 {
    00116   /* Transform from UTF-32 to internal ICU format of UTF-16. */
    00117   int32 length = 0;
    00118   UErrorCode err = U_ZERO_ERROR;
    00119   u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
    00120   return length;
    00121 }
    00122
    00126 class ICUParagraphLayout : public AutoDeleteSmallVector<ParagraphLayouter::Line *, 4>, public ParagraphLayouter {
    00127   ParagraphLayout *p;
    00128 public:
    00130   typedef UChar CharType;
    00132   static const bool SUPPORTS_RTL = true;
    00133
    00135   class ICUVisualRun : public ParagraphLayouter::VisualRun {
    00136     const ParagraphLayout::VisualRun *vr;
    00137
    00138   public:
    00139     ICUVisualRun(const ParagraphLayout::VisualRun *vr) : vr(vr) { }
    00140
    00141     const Font *GetFont() const          { return (const Font*)vr->getFont(); }
    00142     int GetGlyphCount() const            { return vr->getGlyphCount(); }
    00143     const GlyphID *GetGlyphs() const     { return vr->getGlyphs(); }
    00144     const float *GetPositions() const    { return vr->getPositions(); }
    00145     int GetLeading() const               { return vr->getLeading(); }
    00146     const int *GetGlyphToCharMap() const { return vr->getGlyphToCharMap(); }
    00147   };
    00148
    00150   class ICULine : public AutoDeleteSmallVector<ICUVisualRun *, 4>, public ParagraphLayouter::Line {
    00151     ParagraphLayout::Line *l;
    00152
    00153   public:
    00154     ICULine(ParagraphLayout::Line *l) : l(l)
    00155     {
    00156       for (int i = 0; i < l->countRuns(); i++) {
    00157         *this->Append() = new ICUVisualRun(l->getVisualRun(i));
    00158       }
    00159     }
    00160     ~ICULine() { delete l; }
    00161
    00162     int GetLeading() const { return l->getLeading(); }
    00163     int GetWidth() const   { return l->getWidth(); }
    00164     int CountRuns() const  { return l->countRuns(); }
    00165     const ParagraphLayouter::VisualRun *GetVisualRun(int run) const { return *this->Get(run); }
    00166
    00167     int GetInternalCharLength(WChar c) const
    00168     {
    00169       /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */
    00170       return Utf8CharLen(c) < 4 ? 1 : 2;
    00171     }
    00172   };
    00173
    00174   ICUParagraphLayout(ParagraphLayout *p) : p(p) { }
    00175   ~ICUParagraphLayout() { delete p; }
    00176   void Reflow() { p->reflow(); }
    00177
    00178   ParagraphLayouter::Line *NextLine(int max_width)
    00179   {
    00180     ParagraphLayout::Line *l = p->nextLine(max_width);
    00181     return l == NULL ? NULL : new ICULine(l);
    00182   }
    00183 };
    00184
    00185 static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
    00186 {
    00187   int32 length = buff_end - buff;
    00188
    00189   if (length == 0) {
    00190     /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
    00191     buff[0] = ' ';
    00192     length = 1;
    00193     fontMapping.End()[-1].first++;
    00194   }
    00195
    00196   /* Fill ICU's FontRuns with the right data. */
    00197   FontRuns runs(fontMapping.Length());
    00198   for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) {
    00199     runs.add(iter->second, iter->first);
    00200   }
    00201
    00202   LEErrorCode status = LE_NO_ERROR;
    00203   /* ParagraphLayout does not copy "buff", so it must stay valid.
    00204    * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
    00205   ParagraphLayout *p = new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
    00206   if (status != LE_NO_ERROR) {
    00207     delete p;
    00208     return NULL;
    00209   }
    00210
    00211   return new ICUParagraphLayout(p);
    00212 }
    00213
    00214 #endif /* WITH_ICU */
    00215
    00216 /*** Paragraph layout ***/
    00235 class FallbackParagraphLayout : public ParagraphLayouter {
    00236 public:
    00238   typedef WChar CharType;
    00240   static const bool SUPPORTS_RTL = false;
    00241
    00243   class FallbackVisualRun : public ParagraphLayouter::VisualRun {
    00244     Font *font;
    00245     GlyphID *glyphs;
    00246     float *positions;
    00247     int *glyph_to_char;
    00248     int glyph_count;
    00249
    00250   public:
    00251     FallbackVisualRun(Font *font, const WChar *chars, int glyph_count, int x);
    00252     ~FallbackVisualRun();
    00253     const Font *GetFont() const;
    00254     int GetGlyphCount() const;
    00255     const GlyphID *GetGlyphs() const;
    00256     const float *GetPositions() const;
    00257     int GetLeading() const;
    00258     const int *GetGlyphToCharMap() const;
    00259   };
    00260
    00262   class FallbackLine : public AutoDeleteSmallVector<FallbackVisualRun *, 4>, public ParagraphLayouter::Line {
    00263   public:
    00264     int GetLeading() const;
    00265     int GetWidth() const;
    00266     int CountRuns() const;
    00267     const ParagraphLayouter::VisualRun *GetVisualRun(int run) const;
    00268
    00269     int GetInternalCharLength(WChar c) const { return 1; }
    00270   };
    00271
    00272   const WChar *buffer_begin;
    00273   const WChar *buffer;
    00274   FontMap &runs;
    00275
    00276   FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs);
    00277   void Reflow();
    00278   const ParagraphLayouter::Line *NextLine(int max_width);
    00279 };
    00280
    00288 FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const WChar *chars, int char_count, int x) :
    00289     font(font), glyph_count(char_count)
    00290 {
    00291   this->glyphs = MallocT<GlyphID>(this->glyph_count);
    00292   this->glyph_to_char = MallocT<int>(this->glyph_count);
    00293
    00294   /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */
    00295   this->positions = MallocT<float>(this->glyph_count * 2 + 2);
    00296   this->positions[0] = x;
    00297   this->positions[1] = 0;
    00298
    00299   for (int i = 0; i < this->glyph_count; i++) {
    00300     this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]);
    00301     this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]);
    00302     this->positions[2 * i + 3] = 0;
    00303     this->glyph_to_char[i] = i;
    00304   }
    00305 }
    00306
    00308 FallbackParagraphLayout::FallbackVisualRun::~FallbackVisualRun()
    00309 {
    00310   free(this->positions);
    00311   free(this->glyph_to_char);
    00312   free(this->glyphs);
    00313 }
    00314
    00319 const Font *FallbackParagraphLayout::FallbackVisualRun::GetFont() const
    00320 {
    00321   return this->font;
    00322 }
    00323
    00328 int FallbackParagraphLayout::FallbackVisualRun::GetGlyphCount() const
    00329 {
    00330   return this->glyph_count;
    00331 }
    00332
    00337 const GlyphID *FallbackParagraphLayout::FallbackVisualRun::GetGlyphs() const
    00338 {
    00339   return this->glyphs;
    00340 }
    00341
    00346 const float *FallbackParagraphLayout::FallbackVisualRun::GetPositions() const
    00347 {
    00348   return this->positions;
    00349 }
    00350
    00355 const int *FallbackParagraphLayout::FallbackVisualRun::GetGlyphToCharMap() const
    00356 {
    00357   return this->glyph_to_char;
    00358 }
    00359
    00364 int FallbackParagraphLayout::FallbackVisualRun::GetLeading() const
    00365 {
    00366   return this->GetFont()->fc->GetHeight();
    00367 }
    00368
    00373 int FallbackParagraphLayout::FallbackLine::GetLeading() const
    00374 {
    00375   int leading = 0;
    00376   for (const FallbackVisualRun * const *run = this->Begin(); run != this->End(); run++) {
    00377     leading = max(leading, (*run)->GetLeading());
    00378   }
    00379
    00380   return leading;
    00381 }
    00382
    00387 int FallbackParagraphLayout::FallbackLine::GetWidth() const
    00388 {
    00389   if (this->Length() == 0) return 0;
    00390
    00391   /*
    00392    * The last X position of a run contains is the end of that run.
    00393    * Since there is no left-to-right support, taking this value of
    00394    * the last run gives us the end of the line and thus the width.
    00395    */
    00396   const ParagraphLayouter::VisualRun *run = this->GetVisualRun(this->CountRuns() - 1);
    00397   return (int)run->GetPositions()[run->GetGlyphCount() * 2];
    00398 }
    00399
    00404 int FallbackParagraphLayout::FallbackLine::CountRuns() const
    00405 {
    00406   return this->Length();
    00407 }
    00408
    00413 const ParagraphLayouter::VisualRun *FallbackParagraphLayout::FallbackLine::GetVisualRun(int run) const
    00414 {
    00415   return *this->Get(run);
    00416 }
    00417
    00424 FallbackParagraphLayout::FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs)
    00425 {
    00426   assert(runs.End()[-1].first == length);
    00427 }
    00428
    00432 void FallbackParagraphLayout::Reflow()
    00433 {
    00434   this->buffer = this->buffer_begin;
    00435 }
    00436
    00442 const ParagraphLayouter::Line *FallbackParagraphLayout::NextLine(int max_width)
    00443 {
    00444   /* Simple idea:
    00445    *  - split a line at a newline character, or at a space where we can break a line.
    00446    *  - split for a visual run whenever a new line happens, or the font changes.
    00447    */
    00448   if (this->buffer == NULL) return NULL;
    00449
    00450   FallbackLine *l = new FallbackLine();
    00451
    00452   if (*this->buffer == '\0') {
    00453     /* Only a newline. */
    00454     this->buffer = NULL;
    00455     *l->Append() = new FallbackVisualRun(this->runs.Begin()->second, this->buffer, 0, 0);
    00456     return l;
    00457   }
    00458
    00459   const WChar *begin = this->buffer;
    00460   const WChar *last_space = NULL;
    00461   const WChar *last_char = begin;
    00462   int width = 0;
    00463
    00464   int offset = this->buffer - this->buffer_begin;
    00465   FontMap::iterator iter = this->runs.Begin();
    00466   while (iter->first <= offset) {
    00467     iter++;
    00468     assert(iter != this->runs.End());
    00469   }
    00470
    00471   const FontCache *fc = iter->second->fc;
    00472   const WChar *next_run = this->buffer_begin + iter->first;
    00473
    00474   for (;;) {
    00475     WChar c = *this->buffer;
    00476     last_char = this->buffer;
    00477
    00478     if (c == '\0') {
    00479       this->buffer = NULL;
    00480       break;
    00481     }
    00482
    00483     if (this->buffer == next_run) {
    00484       int w = l->GetWidth();
    00485       *l->Append() = new FallbackVisualRun(iter->second, begin, this->buffer - begin, w);
    00486       iter++;
    00487       assert(iter != this->runs.End());
    00488
    00489       next_run = this->buffer_begin + iter->first;
    00490       begin = this->buffer;
    00491
    00492       last_space = NULL;
    00493     }
    00494
    00495     if (IsWhitespace(c)) last_space = this->buffer;
    00496
    00497     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    00498       int char_width = GetCharacterWidth(fc->GetSize(), c);
    00499       width += char_width;
    00500       if (width > max_width) {
    00501         /* The string is longer than maximum width so we need to decide
    00502          * what to do with it. */
    00503         if (width == char_width) {
    00504           /* The character is wider than allowed width; don't know
    00505            * what to do with this case... bail out! */
    00506           this->buffer = NULL;
    00507           return l;
    00508         }
    00509
    00510         if (last_space == NULL) {
    00511           /* No space has been found. Just terminate at our current
    00512            * location. This usually happens for languages that do not
    00513            * require spaces in strings, like Chinese, Japanese and
    00514            * Korean. For other languages terminating mid-word might
    00515            * not be the best, but terminating the whole string instead
    00516            * of continuing the word at the next line is worse. */
    00517           last_char = this->buffer;
    00518         } else {
    00519           /* A space is found; perfect place to terminate */
    00520           this->buffer = last_space + 1;
    00521           last_char = last_space;
    00522         }
    00523         break;
    00524       }
    00525     }
    00526
    00527     this->buffer++;
    00528   }
    00529
    00530   if (l->Length() == 0 || last_char - begin != 0) {
    00531     int w = l->GetWidth();
    00532     *l->Append() = new FallbackVisualRun(iter->second, begin, last_char - begin, w);
    00533   }
    00534   return l;
    00535 }
    00536
    00544 static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
    00545 {
    00546   *buff = c;
    00547   return 1;
    00548 }
    00549
    00557 static FallbackParagraphLayout *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
    00558 {
    00559   return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping);
    00560 }
    00561
    00571 template <typename T>
    00572 static inline void GetLayouter(Layouter::LineCacheItem &line, const char *&str, FontState &state)
    00573 {
    00574   if (line.buffer != NULL) free(line.buffer);
    00575
    00576   typename T::CharType *buff_begin = MallocT<typename T::CharType>(DRAW_STRING_BUFFER);
    00577   const typename T::CharType *buffer_last = buff_begin + DRAW_STRING_BUFFER;
    00578   typename T::CharType *buff = buff_begin;
    00579   FontMap &fontMapping = line.runs;
    00580   Font *f = Layouter::GetFont(state.fontsize, state.cur_colour);
    00581
    00582   line.buffer = buff_begin;
    00583
    00584   /*
    00585    * Go through the whole string while adding Font instances to the font map
    00586    * whenever the font changes, and convert the wide characters into a format
    00587    * usable by ParagraphLayout.
    00588    */
    00589   for (; buff < buffer_last;) {
    00590     WChar c = Utf8Consume(const_cast<const char **>(&str));
    00591     if (c == '\0' || c == '\n') {
    00592       break;
    00593     } else if (c >= SCC_BLUE && c <= SCC_BLACK) {
    00594       state.SetColour((TextColour)(c - SCC_BLUE));
    00595     } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
    00596       state.SetPreviousColour();
    00597     } else if (c == SCC_TINYFONT) {
    00598       state.SetFontSize(FS_SMALL);
    00599     } else if (c == SCC_BIGFONT) {
    00600       state.SetFontSize(FS_LARGE);
    00601     } else {
    00602       /* Filter out text direction characters that shouldn't be drawn, and
    00603        * will not be handled in the fallback non ICU case because they are
    00604        * mostly needed for RTL languages which need more ICU support. */
    00605       if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue;
    00606       buff += AppendToBuffer(buff, buffer_last, c);
    00607       continue;
    00608     }
    00609
    00610     if (!fontMapping.Contains(buff - buff_begin)) {
    00611       fontMapping.Insert(buff - buff_begin, f);
    00612     }
    00613     f = Layouter::GetFont(state.fontsize, state.cur_colour);
    00614   }
    00615
    00616   /* Better safe than sorry. */
    00617   *buff = '\0';
    00618
    00619   if (!fontMapping.Contains(buff - buff_begin)) {
    00620     fontMapping.Insert(buff - buff_begin, f);
    00621   }
    00622   line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
    00623   line.state_after = state;
    00624 }
    00625
    00633 Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) : string(str)
    00634 {
    00635   FontState state(colour, fontsize);
    00636   WChar c = 0;
    00637
    00638   do {
    00639     /* Scan string for end of line */
    00640     const char *lineend = str;
    00641     for (;;) {
    00642       size_t len = Utf8Decode(&c, lineend);
    00643       if (c == '\0' || c == '\n') break;
    00644       lineend += len;
    00645     }
    00646
    00647     LineCacheItem& line = GetCachedParagraphLayout(str, lineend - str, state);
    00648     if (line.layout != NULL) {
    00649       /* Line is in cache */
    00650       str = lineend + 1;
    00651       state = line.state_after;
    00652       line.layout->Reflow();
    00653     } else {
    00654       /* Line is new, layout it */
    00655 #ifdef WITH_ICU
    00656       FontState old_state = state;
    00657       const char *old_str = str;
    00658
    00659       GetLayouter<ICUParagraphLayout>(line, str, state);
    00660       if (line.layout == NULL) {
    00661         static bool warned = false;
    00662         if (!warned) {
    00663           DEBUG(misc, 0, "ICU layouter bailed on the font. Falling back to the fallback layouter");
    00664           warned = true;
    00665         }
    00666
    00667         state = old_state;
    00668         str = old_str;
    00669         GetLayouter<FallbackParagraphLayout>(line, str, state);
    00670       }
    00671 #else
    00672       GetLayouter<FallbackParagraphLayout>(line, str, state);
    00673 #endif
    00674     }
    00675
    00676     /* Copy all lines into a local cache so we can reuse them later on more easily. */
    00677     const ParagraphLayouter::Line *l;
    00678     while ((l = line.layout->NextLine(maxw)) != NULL) {
    00679       *this->Append() = l;
    00680     }
    00681
    00682   } while (c != '\0');
    00683 }
    00684
    00689 Dimension Layouter::GetBounds()
    00690 {
    00691   Dimension d = { 0, 0 };
    00692   for (const ParagraphLayouter::Line **l = this->Begin(); l != this->End(); l++) {
    00693     d.width = max<uint>(d.width, (*l)->GetWidth());
    00694     d.height += (*l)->GetLeading();
    00695   }
    00696   return d;
    00697 }
    00698
    00705 Point Layouter::GetCharPosition(const char *ch) const
    00706 {
    00707   /* Find the code point index which corresponds to the char
    00708    * pointer into our UTF-8 source string. */
    00709   size_t index = 0;
    00710   const char *str = this->string;
    00711   while (str < ch) {
    00712     WChar c;
    00713     size_t len = Utf8Decode(&c, str);
    00714     if (c == '\0' || c == '\n') break;
    00715     str += len;
    00716     index += (*this->Begin())->GetInternalCharLength(c);
    00717   }
    00718
    00719   if (str == ch) {
    00720     /* Valid character. */
    00721     const ParagraphLayouter::Line *line = *this->Begin();
    00722
    00723     /* Pointer to the end-of-string/line marker? Return total line width. */
    00724     if (*ch == '\0' || *ch == '\n') {
    00725       Point p = { line->GetWidth(), 0 };
    00726       return p;
    00727     }
    00728
    00729     /* Scan all runs until we've found our code point index. */
    00730     for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
    00731       const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
    00732
    00733       for (int i = 0; i < run->GetGlyphCount(); i++) {
    00734         /* Matching glyph? Return position. */
    00735         if ((size_t)run->GetGlyphToCharMap()[i] == index) {
    00736           Point p = { (int)run->GetPositions()[i * 2], (int)run->GetPositions()[i * 2 + 1] };
    00737           return p;
    00738         }
    00739       }
    00740     }
    00741   }
    00742
    00743   Point p = { 0, 0 };
    00744   return p;
    00745 }
    00746
    00752 const char *Layouter::GetCharAtPosition(int x) const
    00753 {
    00754   const ParagraphLayouter::Line *line = *this->Begin();
    00755
    00756   for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
    00757     const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
    00758
    00759     for (int i = 0; i < run->GetGlyphCount(); i++) {
    00760       /* Not a valid glyph (empty). */
    00761       if (run->GetGlyphs()[i] == 0xFFFF) continue;
    00762
    00763       int begin_x = (int)run->GetPositions()[i * 2];
    00764       int end_x   = (int)run->GetPositions()[i * 2 + 2];
    00765
    00766       if (IsInsideMM(x, begin_x, end_x)) {
    00767         /* Found our glyph, now convert to UTF-8 string index. */
    00768         size_t index = run->GetGlyphToCharMap()[i];
    00769
    00770         size_t cur_idx = 0;
    00771         for (const char *str = this->string; *str != '\0'; ) {
    00772           if (cur_idx == index) return str;
    00773
    00774           WChar c = Utf8Consume(&str);
    00775           cur_idx += line->GetInternalCharLength(c);
    00776         }
    00777       }
    00778     }
    00779   }
    00780
    00781   return NULL;
    00782 }
    00783
    00787 Font *Layouter::GetFont(FontSize size, TextColour colour)
    00788 {
    00789   FontColourMap::iterator it = fonts[size].Find(colour);
    00790   if (it != fonts[size].End()) return it->second;
    00791
    00792   Font *f = new Font(size, colour);
    00793   *fonts[size].Append() = FontColourMap::Pair(colour, f);
    00794   return f;
    00795 }
    00796
    00801 void Layouter::ResetFontCache(FontSize size)
    00802 {
    00803   for (FontColourMap::iterator it = fonts[size].Begin(); it != fonts[size].End(); ++it) {
    00804     delete it->second;
    00805   }
    00806   fonts[size].Clear();
    00807
    00808   /* We must reset the linecache since it references the just freed fonts */
    00809   ResetLineCache();
    00810 }
    00811
    00820 Layouter::LineCacheItem &Layouter::GetCachedParagraphLayout(const char *str, size_t len, const FontState &state)
    00821 {
    00822   if (linecache == NULL) {
    00823     /* Create linecache on first access to avoid trouble with initialisation order of static variables. */
    00824     linecache = new LineCache();
    00825   }
    00826
    00827   LineCacheKey key;
    00828   key.state_before = state;
    00829   key.str.assign(str, len);
    00830   return (*linecache)[key];
    00831 }
    00832
    00836 void Layouter::ResetLineCache()
    00837 {
    00838   if (linecache != NULL) linecache->clear();
    00839 }
    00840
    00844 void Layouter::ReduceLineCache()
    00845 {
    00846   if (linecache != NULL) {
    00847     /* TODO LRU cache would be fancy, but not exactly necessary */
    00848     if (linecache->size() > 4096) ResetLineCache();
    00849   }
    00850 }