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 }