gfx_layout.cpp

Go to the documentation of this file.
00001 /* $Id: gfx_layout.cpp 25583 2013-07-10 19:41:31Z 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 
00017 #include "table/control_codes.h"
00018 
00019 #ifdef WITH_ICU
00020 #include <unicode/ustring.h>
00021 #endif /* WITH_ICU */
00022 
00023 
00025 Layouter::LineCache *Layouter::linecache;
00026 
00028 Layouter::FontColourMap Layouter::fonts[FS_END];
00029 
00030 
00036 Font::Font(FontSize size, TextColour colour) :
00037     fc(FontCache::Get(size)), colour(colour)
00038 {
00039   assert(size < FS_END);
00040 }
00041 
00042 #ifdef WITH_ICU
00043 /* Implementation details of LEFontInstance */
00044 
00045 le_int32 Font::getUnitsPerEM() const
00046 {
00047   return this->fc->GetUnitsPerEM();
00048 }
00049 
00050 le_int32 Font::getAscent() const
00051 {
00052   return this->fc->GetAscender();
00053 }
00054 
00055 le_int32 Font::getDescent() const
00056 {
00057   return -this->fc->GetDescender();
00058 }
00059 
00060 le_int32 Font::getLeading() const
00061 {
00062   return this->fc->GetHeight();
00063 }
00064 
00065 float Font::getXPixelsPerEm() const
00066 {
00067   return (float)this->fc->GetHeight();
00068 }
00069 
00070 float Font::getYPixelsPerEm() const
00071 {
00072   return (float)this->fc->GetHeight();
00073 }
00074 
00075 float Font::getScaleFactorX() const
00076 {
00077   return 1.0f;
00078 }
00079 
00080 float Font::getScaleFactorY() const
00081 {
00082   return 1.0f;
00083 }
00084 
00085 const void *Font::getFontTable(LETag tableTag) const
00086 {
00087   size_t length;
00088   return this->getFontTable(tableTag, length);
00089 }
00090 
00091 const void *Font::getFontTable(LETag tableTag, size_t &length) const
00092 {
00093   return this->fc->GetFontTable(tableTag, length);
00094 }
00095 
00096 LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const
00097 {
00098   if (IsTextDirectionChar(ch)) return 0;
00099   return this->fc->MapCharToGlyph(ch);
00100 }
00101 
00102 void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const
00103 {
00104   advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph);
00105   advance.fY = 0;
00106 }
00107 
00108 le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const
00109 {
00110   return FALSE;
00111 }
00112 
00113 size_t Layouter::AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
00114 {
00115   /* Transform from UTF-32 to internal ICU format of UTF-16. */
00116   int32 length = 0;
00117   UErrorCode err = U_ZERO_ERROR;
00118   u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
00119   return length;
00120 }
00121 
00122 ParagraphLayout *Layouter::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
00123 {
00124   int32 length = buff_end - buff;
00125 
00126   if (length == 0) {
00127     /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
00128     buff[0] = ' ';
00129     length = 1;
00130     fontMapping.End()[-1].first++;
00131   }
00132 
00133   /* Fill ICU's FontRuns with the right data. */
00134   FontRuns runs(fontMapping.Length());
00135   for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) {
00136     runs.add(iter->second, iter->first);
00137   }
00138 
00139   LEErrorCode status = LE_NO_ERROR;
00140   /* ParagraphLayout does not copy "buff", so it must stay valid.
00141    * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
00142   return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
00143 }
00144 
00145 #else /* WITH_ICU */
00146 
00147 /*** Paragraph layout ***/
00148 
00156 ParagraphLayout::VisualRun::VisualRun(Font *font, const WChar *chars, int char_count, int x) :
00157     font(font), glyph_count(char_count)
00158 {
00159   this->glyphs = MallocT<GlyphID>(this->glyph_count);
00160 
00161   /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */
00162   this->positions = MallocT<float>(this->glyph_count * 2 + 2);
00163   this->positions[0] = x;
00164   this->positions[1] = 0;
00165 
00166   for (int i = 0; i < this->glyph_count; i++) {
00167     this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]);
00168     this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]);
00169     this->positions[2 * i + 3] = 0;
00170   }
00171 }
00172 
00174 ParagraphLayout::VisualRun::~VisualRun()
00175 {
00176   free(this->positions);
00177   free(this->glyphs);
00178 }
00179 
00184 Font *ParagraphLayout::VisualRun::getFont() const
00185 {
00186   return this->font;
00187 }
00188 
00193 int ParagraphLayout::VisualRun::getGlyphCount() const
00194 {
00195   return this->glyph_count;
00196 }
00197 
00202 const GlyphID *ParagraphLayout::VisualRun::getGlyphs() const
00203 {
00204   return this->glyphs;
00205 }
00206 
00211 float *ParagraphLayout::VisualRun::getPositions() const
00212 {
00213   return this->positions;
00214 }
00215 
00220 int ParagraphLayout::VisualRun::getLeading() const
00221 {
00222   return this->getFont()->fc->GetHeight();
00223 }
00224 
00229 int ParagraphLayout::Line::getLeading() const
00230 {
00231   int leading = 0;
00232   for (const VisualRun * const *run = this->Begin(); run != this->End(); run++) {
00233     leading = max(leading, (*run)->getLeading());
00234   }
00235 
00236   return leading;
00237 }
00238 
00243 int ParagraphLayout::Line::getWidth() const
00244 {
00245   if (this->Length() == 0) return 0;
00246 
00247   /*
00248    * The last X position of a run contains is the end of that run.
00249    * Since there is no left-to-right support, taking this value of
00250    * the last run gives us the end of the line and thus the width.
00251    */
00252   const VisualRun *run = this->getVisualRun(this->countRuns() - 1);
00253   return run->getPositions()[run->getGlyphCount() * 2];
00254 }
00255 
00260 int ParagraphLayout::Line::countRuns() const
00261 {
00262   return this->Length();
00263 }
00264 
00269 ParagraphLayout::VisualRun *ParagraphLayout::Line::getVisualRun(int run) const
00270 {
00271   return *this->Get(run);
00272 }
00273 
00280 ParagraphLayout::ParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs)
00281 {
00282   assert(runs.End()[-1].first == length);
00283 }
00284 
00288 void ParagraphLayout::reflow()
00289 {
00290   this->buffer = this->buffer_begin;
00291 }
00292 
00298 ParagraphLayout::Line *ParagraphLayout::nextLine(int max_width)
00299 {
00300   /* Simple idea:
00301    *  - split a line at a newline character, or at a space where we can break a line.
00302    *  - split for a visual run whenever a new line happens, or the font changes.
00303    */
00304   if (this->buffer == NULL) return NULL;
00305 
00306   Line *l = new Line();
00307 
00308   if (*this->buffer == '\0') {
00309     /* Only a newline. */
00310     this->buffer = NULL;
00311     *l->Append() = new VisualRun(this->runs.Begin()->second, this->buffer, 0, 0);
00312     return l;
00313   }
00314 
00315   const WChar *begin = this->buffer;
00316   const WChar *last_space = NULL;
00317   const WChar *last_char = begin;
00318   int width = 0;
00319 
00320   int offset = this->buffer - this->buffer_begin;
00321   FontMap::iterator iter = this->runs.Begin();
00322   while (iter->first <= offset) {
00323     iter++;
00324     assert(iter != this->runs.End());
00325   }
00326 
00327   const FontCache *fc = iter->second->fc;
00328   const WChar *next_run = this->buffer_begin + iter->first;
00329 
00330   for (;;) {
00331     WChar c = *this->buffer;
00332     last_char = this->buffer;
00333 
00334     if (c == '\0') {
00335       this->buffer = NULL;
00336       break;
00337     }
00338 
00339     if (this->buffer == next_run) {
00340       int w = l->getWidth();
00341       *l->Append() = new VisualRun(iter->second, begin, this->buffer - begin, w);
00342       iter++;
00343       assert(iter != this->runs.End());
00344 
00345       next_run = this->buffer_begin + iter->first;
00346       begin = this->buffer;
00347 
00348       last_space = NULL;
00349     }
00350 
00351     if (IsWhitespace(c)) last_space = this->buffer;
00352 
00353     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
00354       int char_width = GetCharacterWidth(fc->GetSize(), c);
00355       width += char_width;
00356       if (width > max_width) {
00357         /* The string is longer than maximum width so we need to decide
00358          * what to do with it. */
00359         if (width == char_width) {
00360           /* The character is wider than allowed width; don't know
00361            * what to do with this case... bail out! */
00362           this->buffer = NULL;
00363           return l;
00364         }
00365 
00366         if (last_space == NULL) {
00367           /* No space has been found. Just terminate at our current
00368            * location. This usually happens for languages that do not
00369            * require spaces in strings, like Chinese, Japanese and
00370            * Korean. For other languages terminating mid-word might
00371            * not be the best, but terminating the whole string instead
00372            * of continuing the word at the next line is worse. */
00373           last_char = this->buffer;
00374         } else {
00375           /* A space is found; perfect place to terminate */
00376           this->buffer = last_space + 1;
00377           last_char = last_space;
00378         }
00379         break;
00380       }
00381     }
00382 
00383     this->buffer++;
00384   }
00385 
00386   if (l->Length() == 0 || last_char - begin != 0) {
00387     int w = l->getWidth();
00388     *l->Append() = new VisualRun(iter->second, begin, last_char - begin, w);
00389   }
00390   return l;
00391 }
00392 
00400 size_t Layouter::AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
00401 {
00402   *buff = c;
00403   return 1;
00404 }
00405 
00413 ParagraphLayout *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
00414 {
00415   return new ParagraphLayout(buff, buff_end - buff, fontMapping);
00416 }
00417 #endif /* !WITH_ICU */
00418 
00426 Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize)
00427 {
00428   FontState state(colour, fontsize);
00429   WChar c = 0;
00430 
00431   do {
00432     /* Scan string for end of line */
00433     const char *lineend = str;
00434     for (;;) {
00435       size_t len = Utf8Decode(&c, lineend);
00436       if (c == '\0' || c == '\n') break;
00437       lineend += len;
00438     }
00439 
00440     LineCacheItem& line = GetCachedParagraphLayout(str, lineend - str, state);
00441     if (line.layout != NULL) {
00442       /* Line is in cache */
00443       str = lineend + 1;
00444       state = line.state_after;
00445       line.layout->reflow();
00446     } else {
00447       /* Line is new, layout it */
00448       const CharType *buffer_last = lastof(line.buffer);
00449       CharType *buff_begin = line.buffer;
00450       CharType *buff = buff_begin;
00451       FontMap &fontMapping = line.runs;
00452       Font *f = GetFont(state.fontsize, state.cur_colour);
00453 
00454       /*
00455        * Go through the whole string while adding Font instances to the font map
00456        * whenever the font changes, and convert the wide characters into a format
00457        * usable by ParagraphLayout.
00458        */
00459       for (; buff < buffer_last;) {
00460         c = Utf8Consume(const_cast<const char **>(&str));
00461         if (c == '\0' || c == '\n') {
00462           break;
00463         } else if (c >= SCC_BLUE && c <= SCC_BLACK) {
00464           state.SetColour((TextColour)(c - SCC_BLUE));
00465         } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
00466           state.SetPreviousColour();
00467         } else if (c == SCC_TINYFONT) {
00468           state.SetFontSize(FS_SMALL);
00469         } else if (c == SCC_BIGFONT) {
00470           state.SetFontSize(FS_LARGE);
00471         } else {
00472           buff += AppendToBuffer(buff, buffer_last, c);
00473           continue;
00474         }
00475 
00476         if (!fontMapping.Contains(buff - buff_begin)) {
00477           fontMapping.Insert(buff - buff_begin, f);
00478         }
00479         f = GetFont(state.fontsize, state.cur_colour);
00480       }
00481 
00482       /* Better safe than sorry. */
00483       *buff = '\0';
00484 
00485       if (!fontMapping.Contains(buff - buff_begin)) {
00486         fontMapping.Insert(buff - buff_begin, f);
00487       }
00488       line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
00489       line.state_after = state;
00490     }
00491 
00492     /* Copy all lines into a local cache so we can reuse them later on more easily. */
00493     ParagraphLayout::Line *l;
00494     while ((l = line.layout->nextLine(maxw)) != NULL) {
00495       *this->Append() = l;
00496     }
00497 
00498   } while (c != '\0');
00499 }
00500 
00505 Dimension Layouter::GetBounds()
00506 {
00507   Dimension d = { 0, 0 };
00508   for (ParagraphLayout::Line **l = this->Begin(); l != this->End(); l++) {
00509     d.width = max<uint>(d.width, (*l)->getWidth());
00510     d.height += (*l)->getLeading();
00511   }
00512   return d;
00513 }
00514 
00518 Font *Layouter::GetFont(FontSize size, TextColour colour)
00519 {
00520   FontColourMap::iterator it = fonts[size].Find(colour);
00521   if (it != fonts[size].End()) return it->second;
00522 
00523   Font *f = new Font(size, colour);
00524   *fonts[size].Append() = FontColourMap::Pair(colour, f);
00525   return f;
00526 }
00527 
00532 void Layouter::ResetFontCache(FontSize size)
00533 {
00534   for (FontColourMap::iterator it = fonts[size].Begin(); it != fonts[size].End(); ++it) {
00535     delete it->second;
00536   }
00537   fonts[size].Clear();
00538 
00539   /* We must reset the linecache since it references the just freed fonts */
00540   ResetLineCache();
00541 }
00542 
00551 Layouter::LineCacheItem &Layouter::GetCachedParagraphLayout(const char *str, size_t len, const FontState &state)
00552 {
00553   if (linecache == NULL) {
00554     /* Create linecache on first access to avoid trouble with initialisation order of static variables. */
00555     linecache = new LineCache();
00556   }
00557 
00558   LineCacheKey key;
00559   key.state_before = state;
00560   key.str.assign(str, len);
00561   return (*linecache)[key];
00562 }
00563 
00567 void Layouter::ResetLineCache()
00568 {
00569   if (linecache != NULL) linecache->clear();
00570 }
00571 
00575 void Layouter::ReduceLineCache()
00576 {
00577   if (linecache != NULL) {
00578     /* TODO LRU cache would be fancy, but not exactly necessary */
00579     if (linecache->size() > 4096) ResetLineCache();
00580   }
00581 }