gfx_layout.cpp

Go to the documentation of this file.
00001 /* $Id: gfx_layout.cpp 25528 2013-06-30 08:58:35Z 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 
00028 Font::Font(FontSize size, TextColour colour) :
00029     fc(FontCache::Get(size)), colour(colour)
00030 {
00031   assert(size < FS_END);
00032 }
00033 
00034 #ifdef WITH_ICU
00035 /* Implementation details of LEFontInstance */
00036 
00037 le_int32 Font::getUnitsPerEM() const
00038 {
00039   return this->fc->GetUnitsPerEM();
00040 }
00041 
00042 le_int32 Font::getAscent() const
00043 {
00044   return this->fc->GetAscender();
00045 }
00046 
00047 le_int32 Font::getDescent() const
00048 {
00049   return -this->fc->GetDescender();
00050 }
00051 
00052 le_int32 Font::getLeading() const
00053 {
00054   return this->fc->GetHeight();
00055 }
00056 
00057 float Font::getXPixelsPerEm() const
00058 {
00059   return (float)this->fc->GetHeight();
00060 }
00061 
00062 float Font::getYPixelsPerEm() const
00063 {
00064   return (float)this->fc->GetHeight();
00065 }
00066 
00067 float Font::getScaleFactorX() const
00068 {
00069   return 1.0f;
00070 }
00071 
00072 float Font::getScaleFactorY() const
00073 {
00074   return 1.0f;
00075 }
00076 
00077 const void *Font::getFontTable(LETag tableTag) const
00078 {
00079   size_t length;
00080   return this->getFontTable(tableTag, length);
00081 }
00082 
00083 const void *Font::getFontTable(LETag tableTag, size_t &length) const
00084 {
00085   return this->fc->GetFontTable(tableTag, length);
00086 }
00087 
00088 LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const
00089 {
00090   if (IsTextDirectionChar(ch)) return 0;
00091   return this->fc->MapCharToGlyph(ch);
00092 }
00093 
00094 void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const
00095 {
00096   advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph);
00097   advance.fY = 0;
00098 }
00099 
00100 le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const
00101 {
00102   return FALSE;
00103 }
00104 
00105 size_t Layouter::AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
00106 {
00107   /* Transform from UTF-32 to internal ICU format of UTF-16. */
00108   int32 length = 0;
00109   UErrorCode err = U_ZERO_ERROR;
00110   u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
00111   return length;
00112 }
00113 
00114 ParagraphLayout *Layouter::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
00115 {
00116   int32 length = buff_end - buff;
00117 
00118   if (length == 0) {
00119     /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
00120     buff[0] = ' ';
00121     length = 1;
00122     fontMapping.End()[-1].first++;
00123   }
00124 
00125   /* Fill ICU's FontRuns with the right data. */
00126   FontRuns runs(fontMapping.Length());
00127   for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) {
00128     runs.add(iter->second, iter->first);
00129   }
00130 
00131   LEErrorCode status = LE_NO_ERROR;
00132   return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
00133 }
00134 
00135 #else /* WITH_ICU */
00136 
00137 /*** Paragraph layout ***/
00138 
00146 ParagraphLayout::VisualRun::VisualRun(Font *font, const WChar *chars, int char_count, int x) :
00147     font(font), glyph_count(char_count)
00148 {
00149   this->glyphs = MallocT<GlyphID>(this->glyph_count);
00150 
00151   /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */
00152   this->positions = MallocT<float>(this->glyph_count * 2 + 2);
00153   this->positions[0] = x;
00154   this->positions[1] = 0;
00155 
00156   for (int i = 0; i < this->glyph_count; i++) {
00157     this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]);
00158     this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]);
00159     this->positions[2 * i + 3] = 0;
00160   }
00161 }
00162 
00164 ParagraphLayout::VisualRun::~VisualRun()
00165 {
00166   free(this->positions);
00167   free(this->glyphs);
00168 }
00169 
00174 Font *ParagraphLayout::VisualRun::getFont() const
00175 {
00176   return this->font;
00177 }
00178 
00183 int ParagraphLayout::VisualRun::getGlyphCount() const
00184 {
00185   return this->glyph_count;
00186 }
00187 
00192 const GlyphID *ParagraphLayout::VisualRun::getGlyphs() const
00193 {
00194   return this->glyphs;
00195 }
00196 
00201 float *ParagraphLayout::VisualRun::getPositions() const
00202 {
00203   return this->positions;
00204 }
00205 
00210 int ParagraphLayout::VisualRun::getLeading() const
00211 {
00212   return this->getFont()->fc->GetHeight();
00213 }
00214 
00219 int ParagraphLayout::Line::getLeading() const
00220 {
00221   int leading = 0;
00222   for (const VisualRun * const *run = this->Begin(); run != this->End(); run++) {
00223     leading = max(leading, (*run)->getLeading());
00224   }
00225 
00226   return leading;
00227 }
00228 
00233 int ParagraphLayout::Line::getWidth() const
00234 {
00235   if (this->Length() == 0) return 0;
00236 
00237   /*
00238    * The last X position of a run contains is the end of that run.
00239    * Since there is no left-to-right support, taking this value of
00240    * the last run gives us the end of the line and thus the width.
00241    */
00242   const VisualRun *run = this->getVisualRun(this->countRuns() - 1);
00243   return run->getPositions()[run->getGlyphCount() * 2];
00244 }
00245 
00250 int ParagraphLayout::Line::countRuns() const
00251 {
00252   return this->Length();
00253 }
00254 
00259 ParagraphLayout::VisualRun *ParagraphLayout::Line::getVisualRun(int run) const
00260 {
00261   return *this->Get(run);
00262 }
00263 
00270 ParagraphLayout::ParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs)
00271 {
00272   assert(runs.End()[-1].first == length);
00273 }
00274 
00280 ParagraphLayout::Line *ParagraphLayout::nextLine(int max_width)
00281 {
00282   /* Simple idea:
00283    *  - split a line at a newline character, or at a space where we can break a line.
00284    *  - split for a visual run whenever a new line happens, or the font changes.
00285    */
00286   if (this->buffer == NULL) return NULL;
00287 
00288   Line *l = new Line();
00289 
00290   if (*this->buffer == '\0') {
00291     /* Only a newline. */
00292     this->buffer = NULL;
00293     *l->Append() = new VisualRun(this->runs.Begin()->second, this->buffer, 0, 0);
00294     return l;
00295   }
00296 
00297   const WChar *begin = this->buffer;
00298   WChar *last_space = NULL;
00299   const WChar *last_char = begin;
00300   int width = 0;
00301 
00302   int offset = this->buffer - this->buffer_begin;
00303   FontMap::iterator iter = this->runs.Begin();
00304   while (iter->first <= offset) {
00305     iter++;
00306     assert(iter != this->runs.End());
00307   }
00308 
00309   const FontCache *fc = iter->second->fc;
00310   const WChar *next_run = this->buffer_begin + iter->first;
00311 
00312   for (;;) {
00313     WChar c = *this->buffer;
00314     last_char = this->buffer;
00315 
00316     if (c == '\0') {
00317       this->buffer = NULL;
00318       break;
00319     }
00320 
00321     if (this->buffer == next_run) {
00322       *l->Append() = new VisualRun(iter->second, begin, this->buffer - begin, l->getWidth());
00323       iter++;
00324       assert(iter != this->runs.End());
00325 
00326       next_run = this->buffer_begin + iter->first;
00327       begin = this->buffer;
00328 
00329       last_space = NULL;
00330     }
00331 
00332     if (IsWhitespace(c)) last_space = this->buffer;
00333 
00334     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
00335       int char_width = GetCharacterWidth(fc->GetSize(), c);
00336       width += char_width;
00337       if (width > max_width) {
00338         /* The string is longer than maximum width so we need to decide
00339          * what to do with it. */
00340         if (width == char_width) {
00341           /* The character is wider than allowed width; don't know
00342            * what to do with this case... bail out! */
00343           this->buffer = NULL;
00344           return l;
00345         }
00346 
00347         if (last_space == NULL) {
00348           /* No space has been found. Just terminate at our current
00349            * location. This usually happens for languages that do not
00350            * require spaces in strings, like Chinese, Japanese and
00351            * Korean. For other languages terminating mid-word might
00352            * not be the best, but terminating the whole string instead
00353            * of continuing the word at the next line is worse. */
00354           last_char = this->buffer;
00355         } else {
00356           /* A space is found; perfect place to terminate */
00357           this->buffer = last_space;
00358           last_char = last_space;
00359         }
00360         break;
00361       }
00362     }
00363 
00364     this->buffer++;
00365   }
00366 
00367   if (l->Length() == 0 || last_char - begin != 0) {
00368     *l->Append() = new VisualRun(iter->second, begin, last_char - begin, l->getWidth());
00369   }
00370   return l;
00371 }
00372 
00380 size_t Layouter::AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
00381 {
00382   *buff = c;
00383   return 1;
00384 }
00385 
00393 ParagraphLayout *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
00394 {
00395   return new ParagraphLayout(buff, buff_end - buff, fontMapping);
00396 }
00397 #endif /* !WITH_ICU */
00398 
00406 Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize)
00407 {
00408   const CharType *buffer_last = lastof(this->buffer);
00409   CharType *buff = this->buffer;
00410 
00411   TextColour cur_colour = colour, prev_colour = colour;
00412   WChar c = 0;
00413 
00414   do {
00415     Font *f = new Font(fontsize, cur_colour);
00416     CharType *buff_begin = buff;
00417     FontMap fontMapping;
00418 
00419     /*
00420      * Go through the whole string while adding Font instances to the font map
00421      * whenever the font changes, and convert the wide characters into a format
00422      * usable by ParagraphLayout.
00423      */
00424     for (; buff < buffer_last;) {
00425       c = Utf8Consume(const_cast<const char **>(&str));
00426       if (c == '\0' || c == '\n') {
00427         break;
00428       } else if (c >= SCC_BLUE && c <= SCC_BLACK) {
00429         prev_colour = cur_colour;
00430         cur_colour = (TextColour)(c - SCC_BLUE);
00431       } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
00432         Swap(prev_colour, cur_colour);
00433       } else if (c == SCC_TINYFONT) {
00434         fontsize = FS_SMALL;
00435       } else if (c == SCC_BIGFONT) {
00436         fontsize = FS_LARGE;
00437       } else {
00438         buff += AppendToBuffer(buff, buffer_last, c);
00439         continue;
00440       }
00441 
00442       if (!fontMapping.Contains(buff - buff_begin)) {
00443         fontMapping.Insert(buff - buff_begin, f);
00444         *this->fonts.Append() = f;
00445       } else {
00446         delete f;
00447       }
00448       f = new Font(fontsize, cur_colour);
00449     }
00450 
00451     /* Better safe than sorry. */
00452     *buff = '\0';
00453 
00454     if (!fontMapping.Contains(buff - buff_begin)) {
00455       fontMapping.Insert(buff - buff_begin, f);
00456       *this->fonts.Append() = f;
00457     }
00458     ParagraphLayout *p = GetParagraphLayout(buff_begin, buff, fontMapping);
00459 
00460     /* Copy all lines into a local cache so we can reuse them later on more easily. */
00461     ParagraphLayout::Line *l;
00462     while ((l = p->nextLine(maxw)) != NULL) {
00463       *this->Append() = l;
00464     }
00465 
00466     delete p;
00467 
00468   } while (c != '\0' && buff < buffer_last);
00469 }
00470 
00472 Layouter::~Layouter()
00473 {
00474   for (Font **iter = this->fonts.Begin(); iter != this->fonts.End(); iter++) {
00475     delete *iter;
00476   }
00477 }
00478 
00483 Dimension Layouter::GetBounds()
00484 {
00485   Dimension d = { 0, 0 };
00486   for (ParagraphLayout::Line **l = this->Begin(); l != this->End(); l++) {
00487     d.width = max<uint>(d.width, (*l)->getWidth());
00488     d.height += (*l)->getLeading();
00489   }
00490   return d;
00491 }