gfx_layout.cpp

Go to the documentation of this file.
00001 /* $Id: gfx_layout.cpp 26032 2013-11-17 17:31:51Z 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       GetLayouter<ICUParagraphLayout>(line, str, state);
00657       if (line.layout == NULL) {
00658         static bool warned = false;
00659         if (!warned) {
00660           DEBUG(misc, 0, "ICU layouter bailed on the font. Falling back to the fallback layouter");
00661           warned = true;
00662         }
00663         GetLayouter<FallbackParagraphLayout>(line, str, state);
00664       }
00665 #else
00666       GetLayouter<FallbackParagraphLayout>(line, str, state);
00667 #endif
00668     }
00669 
00670     /* Copy all lines into a local cache so we can reuse them later on more easily. */
00671     const ParagraphLayouter::Line *l;
00672     while ((l = line.layout->NextLine(maxw)) != NULL) {
00673       *this->Append() = l;
00674     }
00675 
00676   } while (c != '\0');
00677 }
00678 
00683 Dimension Layouter::GetBounds()
00684 {
00685   Dimension d = { 0, 0 };
00686   for (const ParagraphLayouter::Line **l = this->Begin(); l != this->End(); l++) {
00687     d.width = max<uint>(d.width, (*l)->GetWidth());
00688     d.height += (*l)->GetLeading();
00689   }
00690   return d;
00691 }
00692 
00699 Point Layouter::GetCharPosition(const char *ch) const
00700 {
00701   /* Find the code point index which corresponds to the char
00702    * pointer into our UTF-8 source string. */
00703   size_t index = 0;
00704   const char *str = this->string;
00705   while (str < ch) {
00706     WChar c;
00707     size_t len = Utf8Decode(&c, str);
00708     if (c == '\0' || c == '\n') break;
00709     str += len;
00710     index += (*this->Begin())->GetInternalCharLength(c);
00711   }
00712 
00713   if (str == ch) {
00714     /* Valid character. */
00715     const ParagraphLayouter::Line *line = *this->Begin();
00716 
00717     /* Pointer to the end-of-string/line marker? Return total line width. */
00718     if (*ch == '\0' || *ch == '\n') {
00719       Point p = { line->GetWidth(), 0 };
00720       return p;
00721     }
00722 
00723     /* Scan all runs until we've found our code point index. */
00724     for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
00725       const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
00726 
00727       for (int i = 0; i < run->GetGlyphCount(); i++) {
00728         /* Matching glyph? Return position. */
00729         if ((size_t)run->GetGlyphToCharMap()[i] == index) {
00730           Point p = { (int)run->GetPositions()[i * 2], (int)run->GetPositions()[i * 2 + 1] };
00731           return p;
00732         }
00733       }
00734     }
00735   }
00736 
00737   Point p = { 0, 0 };
00738   return p;
00739 }
00740 
00746 const char *Layouter::GetCharAtPosition(int x) const
00747 {
00748   const ParagraphLayouter::Line *line = *this->Begin();;
00749 
00750   for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
00751     const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
00752 
00753     for (int i = 0; i < run->GetGlyphCount(); i++) {
00754       /* Not a valid glyph (empty). */
00755       if (run->GetGlyphs()[i] == 0xFFFF) continue;
00756 
00757       int begin_x = (int)run->GetPositions()[i * 2];
00758       int end_x   = (int)run->GetPositions()[i * 2 + 2];
00759 
00760       if (IsInsideMM(x, begin_x, end_x)) {
00761         /* Found our glyph, now convert to UTF-8 string index. */
00762         size_t index = run->GetGlyphToCharMap()[i];
00763 
00764         size_t cur_idx = 0;
00765         for (const char *str = this->string; *str != '\0'; ) {
00766           if (cur_idx == index) return str;
00767 
00768           WChar c = Utf8Consume(&str);
00769           cur_idx += line->GetInternalCharLength(c);
00770         }
00771       }
00772     }
00773   }
00774 
00775   return NULL;
00776 }
00777 
00781 Font *Layouter::GetFont(FontSize size, TextColour colour)
00782 {
00783   FontColourMap::iterator it = fonts[size].Find(colour);
00784   if (it != fonts[size].End()) return it->second;
00785 
00786   Font *f = new Font(size, colour);
00787   *fonts[size].Append() = FontColourMap::Pair(colour, f);
00788   return f;
00789 }
00790 
00795 void Layouter::ResetFontCache(FontSize size)
00796 {
00797   for (FontColourMap::iterator it = fonts[size].Begin(); it != fonts[size].End(); ++it) {
00798     delete it->second;
00799   }
00800   fonts[size].Clear();
00801 
00802   /* We must reset the linecache since it references the just freed fonts */
00803   ResetLineCache();
00804 }
00805 
00814 Layouter::LineCacheItem &Layouter::GetCachedParagraphLayout(const char *str, size_t len, const FontState &state)
00815 {
00816   if (linecache == NULL) {
00817     /* Create linecache on first access to avoid trouble with initialisation order of static variables. */
00818     linecache = new LineCache();
00819   }
00820 
00821   LineCacheKey key;
00822   key.state_before = state;
00823   key.str.assign(str, len);
00824   return (*linecache)[key];
00825 }
00826 
00830 void Layouter::ResetLineCache()
00831 {
00832   if (linecache != NULL) linecache->clear();
00833 }
00834 
00838 void Layouter::ReduceLineCache()
00839 {
00840   if (linecache != NULL) {
00841     /* TODO LRU cache would be fancy, but not exactly necessary */
00842     if (linecache->size() > 4096) ResetLineCache();
00843   }
00844 }