# 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 }