vdr  1.7.27
font.c
Go to the documentation of this file.
00001 /*
00002  * font.c: Font handling for the DVB On Screen Display
00003  *
00004  * See the main source file 'vdr.c' for copyright information and
00005  * how to reach the author.
00006  *
00007  * BiDi support by Osama Alrawab <alrawab@hotmail.com> @2008 Tripoli-Libya.
00008  *
00009  * $Id: font.c 2.10 2012/03/02 10:47:45 kls Exp $
00010  */
00011 
00012 #include "font.h"
00013 #include <ctype.h>
00014 #include <fontconfig/fontconfig.h>
00015 #ifdef BIDI
00016 #include <fribidi.h>
00017 #endif
00018 #include <ft2build.h>
00019 #include FT_FREETYPE_H
00020 #include "config.h"
00021 #include "osd.h"
00022 #include "tools.h"
00023 
00024 const char *DefaultFontOsd = "Sans Serif:Bold";
00025 const char *DefaultFontSml = "Sans Serif";
00026 const char *DefaultFontFix = "Courier:Bold";
00027 
00028 // --- cFreetypeFont ---------------------------------------------------------
00029 
00030 #define KERNING_UNKNOWN  (-10000)
00031 
00032 struct tKerning {
00033   uint prevSym;
00034   int kerning;
00035   tKerning(uint PrevSym, int Kerning = 0) { prevSym = PrevSym; kerning = Kerning; }
00036   };
00037 
00038 class cGlyph : public cListObject {
00039 private:
00040   uint charCode;
00041   uchar *bitmap;
00042   int advanceX;
00043   int advanceY;
00044   int left;  
00045   int top;   
00046   int width; 
00047   int rows;  
00048   int pitch; 
00049   cVector<tKerning> kerningCache;
00050 public:
00051   cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData);
00052   virtual ~cGlyph();
00053   uint CharCode(void) const { return charCode; }
00054   uchar *Bitmap(void) const { return bitmap; }
00055   int AdvanceX(void) const { return advanceX; }
00056   int AdvanceY(void) const { return advanceY; }
00057   int Left(void) const { return left; }
00058   int Top(void) const { return top; }
00059   int Width(void) const { return width; }
00060   int Rows(void) const { return rows; }
00061   int Pitch(void) const { return pitch; }
00062   int GetKerningCache(uint PrevSym) const;
00063   void SetKerningCache(uint PrevSym, int Kerning);
00064   };
00065 
00066 cGlyph::cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData)
00067 {
00068   charCode = CharCode;
00069   advanceX = GlyphData->advance.x >> 6;
00070   advanceY = GlyphData->advance.y >> 6;
00071   left = GlyphData->bitmap_left;
00072   top = GlyphData->bitmap_top;
00073   width = GlyphData->bitmap.width;
00074   rows = GlyphData->bitmap.rows;
00075   pitch = GlyphData->bitmap.pitch;
00076   bitmap = MALLOC(uchar, rows * pitch);
00077   memcpy(bitmap, GlyphData->bitmap.buffer, rows * pitch);
00078 }
00079 
00080 cGlyph::~cGlyph()
00081 {
00082   free(bitmap);
00083 }
00084 
00085 int cGlyph::GetKerningCache(uint PrevSym) const
00086 {
00087   for (int i = kerningCache.Size(); --i > 0; ) {
00088       if (kerningCache[i].prevSym == PrevSym)
00089          return kerningCache[i].kerning;
00090       }
00091   return KERNING_UNKNOWN;
00092 }
00093 
00094 void cGlyph::SetKerningCache(uint PrevSym, int Kerning)
00095 {
00096   kerningCache.Append(tKerning(PrevSym, Kerning));
00097 }
00098 
00099 class cFreetypeFont : public cFont {
00100 private:
00101   cString fontName;
00102   int size;
00103   int height;
00104   int bottom;
00105   FT_Library library; 
00106   FT_Face face; 
00107   mutable cList<cGlyph> glyphCacheMonochrome;
00108   mutable cList<cGlyph> glyphCacheAntiAliased;
00109   int Bottom(void) const { return bottom; }
00110   int Kerning(cGlyph *Glyph, uint PrevSym) const;
00111   cGlyph* Glyph(uint CharCode, bool AntiAliased = false) const;
00112 public:
00113   cFreetypeFont(const char *Name, int CharHeight, int CharWidth = 0);
00114   virtual ~cFreetypeFont();
00115   virtual const char *FontName(void) const { return fontName; }
00116   virtual int Size(void) const { return size; }
00117   virtual int Width(uint c) const;
00118   virtual int Width(const char *s) const;
00119   virtual int Height(void) const { return height; }
00120   virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const;
00121   virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const;
00122   };
00123 
00124 cFreetypeFont::cFreetypeFont(const char *Name, int CharHeight, int CharWidth)
00125 {
00126   fontName = Name;
00127   size = CharHeight;
00128   height = 0;
00129   bottom = 0;
00130   int error = FT_Init_FreeType(&library);
00131   if (!error) {
00132      error = FT_New_Face(library, Name, 0, &face);
00133      if (!error) {
00134         if (face->num_fixed_sizes && face->available_sizes) { // fixed font
00135            // TODO what exactly does all this mean?
00136            height = face->available_sizes->height;
00137            for (uint sym ='A'; sym < 'z'; sym++) { // search for descender for fixed font FIXME
00138                FT_UInt glyph_index = FT_Get_Char_Index(face, sym);
00139                error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
00140                if (!error) {
00141                   error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
00142                   if (!error) {
00143                      if (face->glyph->bitmap.rows-face->glyph->bitmap_top > bottom)
00144                         bottom = face->glyph->bitmap.rows-face->glyph->bitmap_top;
00145                      }
00146                   else
00147                      esyslog("ERROR: FreeType: error %d in FT_Render_Glyph", error);
00148                   }
00149                else
00150                   esyslog("ERROR: FreeType: error %d in FT_Load_Glyph", error);
00151                }
00152            }
00153         else {
00154            error = FT_Set_Char_Size(face, // handle to face object
00155                                     CharWidth * 64,  // CharWidth in 1/64th of points
00156                                     CharHeight * 64, // CharHeight in 1/64th of points
00157                                     0,    // horizontal device resolution
00158                                     0);   // vertical device resolution
00159            if (!error) {
00160               height = (face->size->metrics.ascender - face->size->metrics.descender + 63) / 64;
00161               bottom = abs((face->size->metrics.descender - 63) / 64);
00162               }
00163            else
00164               esyslog("ERROR: FreeType: error %d during FT_Set_Char_Size (font = %s)\n", error, Name);
00165            }
00166         }
00167      else
00168         esyslog("ERROR: FreeType: load error %d (font = %s)", error, Name);
00169      }
00170   else
00171      esyslog("ERROR: FreeType: initialization error %d (font = %s)", error, Name);
00172 }
00173 
00174 cFreetypeFont::~cFreetypeFont()
00175 {
00176   FT_Done_Face(face);
00177   FT_Done_FreeType(library);
00178 }
00179 
00180 int cFreetypeFont::Kerning(cGlyph *Glyph, uint PrevSym) const
00181 {
00182   int kerning = 0;
00183   if (Glyph && PrevSym) {
00184      kerning = Glyph->GetKerningCache(PrevSym);
00185      if (kerning == KERNING_UNKNOWN) {
00186         FT_Vector delta;
00187         FT_UInt glyph_index = FT_Get_Char_Index(face, Glyph->CharCode());
00188         FT_UInt glyph_index_prev = FT_Get_Char_Index(face, PrevSym);
00189         FT_Get_Kerning(face, glyph_index_prev, glyph_index, FT_KERNING_DEFAULT, &delta);
00190         kerning = delta.x / 64;
00191         Glyph->SetKerningCache(PrevSym, kerning);
00192         }
00193      }
00194   return kerning;
00195 }
00196 
00197 cGlyph* cFreetypeFont::Glyph(uint CharCode, bool AntiAliased) const
00198 {
00199   // Non-breaking space:
00200   if (CharCode == 0xA0)
00201      CharCode = 0x20;
00202 
00203   // Lookup in cache:
00204   cList<cGlyph> *glyphCache = AntiAliased ? &glyphCacheAntiAliased : &glyphCacheMonochrome;
00205   for (cGlyph *g = glyphCache->First(); g; g = glyphCache->Next(g)) {
00206       if (g->CharCode() == CharCode)
00207          return g;
00208       }
00209 
00210   FT_UInt glyph_index = FT_Get_Char_Index(face, CharCode);
00211 
00212   // Load glyph image into the slot (erase previous one):
00213   int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
00214   if (error)
00215      esyslog("ERROR: FreeType: error during FT_Load_Glyph");
00216   else {
00217 #if ((FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 7) || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 2 && FREETYPE_PATCH <= 1))// TODO workaround for bug? which one?
00218      if (AntiAliased || CharCode == 32)
00219 #else
00220      if (AntiAliased)
00221 #endif
00222         error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
00223      else
00224         error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO);
00225      if (error)
00226         esyslog("ERROR: FreeType: error during FT_Render_Glyph %d, %d\n", CharCode, glyph_index);
00227      else { //new bitmap
00228         cGlyph *Glyph = new cGlyph(CharCode, face->glyph);
00229         glyphCache->Add(Glyph);
00230         return Glyph;
00231         }
00232      }
00233 #define UNKNOWN_GLYPH_INDICATOR '?'
00234   if (CharCode != UNKNOWN_GLYPH_INDICATOR)
00235      return Glyph(UNKNOWN_GLYPH_INDICATOR, AntiAliased);
00236   return NULL;
00237 }
00238 
00239 int cFreetypeFont::Width(uint c) const
00240 {
00241   cGlyph *g = Glyph(c, Setup.AntiAlias);
00242   return g ? g->AdvanceX() : 0;
00243 }
00244 
00245 int cFreetypeFont::Width(const char *s) const
00246 {
00247   int w = 0;
00248   if (s) {
00249 #ifdef BIDI
00250      cString bs = Bidi(s);
00251      s = bs;
00252 #endif
00253      uint prevSym = 0;
00254      while (*s) {
00255            int sl = Utf8CharLen(s);
00256            uint sym = Utf8CharGet(s, sl);
00257            s += sl;
00258            cGlyph *g = Glyph(sym, Setup.AntiAlias);
00259            if (g)
00260               w += g->AdvanceX() + Kerning(g, prevSym);
00261            prevSym = sym;
00262            }
00263      }
00264   return w;
00265 }
00266 
00267 #define MAX_BLEND_LEVELS 256
00268 
00269 void cFreetypeFont::DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const
00270 {
00271   if (s && height) { // checking height to make sure we actually have a valid font
00272 #ifdef BIDI
00273      cString bs = Bidi(s);
00274      s = bs;
00275 #endif
00276      bool AntiAliased = Setup.AntiAlias && Bitmap->Bpp() >= 8;
00277      bool TransparentBackground = ColorBg == clrTransparent;
00278      int16_t BlendLevelIndex[MAX_BLEND_LEVELS]; // tIndex is 8 bit unsigned, so a negative value can be used to mark unused entries
00279      if (AntiAliased && !TransparentBackground)
00280         memset(BlendLevelIndex, 0xFF, sizeof(BlendLevelIndex)); // initializes the array with negative values
00281      tIndex fg = Bitmap->Index(ColorFg);
00282      uint prevSym = 0;
00283      while (*s) {
00284            int sl = Utf8CharLen(s);
00285            uint sym = Utf8CharGet(s, sl);
00286            s += sl;
00287            cGlyph *g = Glyph(sym, AntiAliased);
00288            if (!g)
00289               continue;
00290            int kerning = Kerning(g, prevSym);
00291            prevSym = sym;
00292            uchar *buffer = g->Bitmap();
00293            int symWidth = g->Width();
00294            if (Width && x + symWidth + g->Left() + kerning - 1 > Width)
00295               break; // we don't draw partial characters
00296            if (x + symWidth + g->Left() + kerning > 0) {
00297               for (int row = 0; row < g->Rows(); row++) {
00298                   for (int pitch = 0; pitch < g->Pitch(); pitch++) {
00299                       uchar bt = *(buffer + (row * g->Pitch() + pitch));
00300                       if (AntiAliased) {
00301                          if (bt > 0x00) {
00302                             int px = x + pitch + g->Left() + kerning;
00303                             int py = y + row + (height - Bottom() - g->Top());
00304                             tColor bg;
00305                             if (bt == 0xFF)
00306                                bg = fg;
00307                             else if (TransparentBackground)
00308                                bg = Bitmap->Index(Bitmap->Blend(ColorFg, Bitmap->GetColor(px, py), bt));
00309                             else if (BlendLevelIndex[bt] >= 0)
00310                                bg = BlendLevelIndex[bt];
00311                             else
00312                                bg = BlendLevelIndex[bt] = Bitmap->Index(Bitmap->Blend(ColorFg, ColorBg, bt));
00313                             Bitmap->SetIndex(px, py, bg);
00314                             }
00315                          }
00316                       else { //monochrome rendering
00317                          for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) {
00318                              if (bt & 0x80)
00319                                 Bitmap->SetIndex(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top()), fg);
00320                              bt <<= 1;
00321                              }
00322                          }
00323                       }
00324                   }
00325               }
00326            x += g->AdvanceX() + kerning;
00327            if (x > Bitmap->Width() - 1)
00328               break;
00329            }
00330      }
00331 }
00332 
00333 void cFreetypeFont::DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const
00334 {
00335   if (s && height) { // checking height to make sure we actually have a valid font
00336 #ifdef BIDI
00337      cString bs = Bidi(s);
00338      s = bs;
00339 #endif
00340      bool AntiAliased = Setup.AntiAlias;
00341      uint prevSym = 0;
00342      while (*s) {
00343            int sl = Utf8CharLen(s);
00344            uint sym = Utf8CharGet(s, sl);
00345            s += sl;
00346            cGlyph *g = Glyph(sym, AntiAliased);
00347            if (!g)
00348               continue;
00349            int kerning = Kerning(g, prevSym);
00350            prevSym = sym;
00351            uchar *buffer = g->Bitmap();
00352            int symWidth = g->Width();
00353            if (Width && x + symWidth + g->Left() + kerning - 1 > Width)
00354               break; // we don't draw partial characters
00355            if (x + symWidth + g->Left() + kerning > 0) {
00356               for (int row = 0; row < g->Rows(); row++) {
00357                   for (int pitch = 0; pitch < g->Pitch(); pitch++) {
00358                       uchar bt = *(buffer + (row * g->Pitch() + pitch));
00359                       if (AntiAliased) {
00360                          if (bt > 0x00)
00361                             Pixmap->DrawPixel(cPoint(x + pitch + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), AlphaBlend(ColorFg, ColorBg, bt));
00362                          }
00363                       else { //monochrome rendering
00364                          for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) {
00365                              if (bt & 0x80)
00366                                 Pixmap->DrawPixel(cPoint(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), ColorFg);
00367                              bt <<= 1;
00368                              }
00369                          }
00370                       }
00371                   }
00372               }
00373            x += g->AdvanceX() + kerning;
00374            if (x > Pixmap->DrawPort().Width() - 1)
00375               break;
00376            }
00377      }
00378 }
00379 
00380 // --- cDummyFont ------------------------------------------------------------
00381 
00382 // A dummy font, in case there are no fonts installed:
00383 
00384 class cDummyFont : public cFont {
00385 public:
00386   virtual int Width(uint c) const { return 10; }
00387   virtual int Width(const char *s) const { return 50; }
00388   virtual int Height(void) const { return 20; }
00389   virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {}
00390   virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {};
00391   };
00392 
00393 // --- cFont -----------------------------------------------------------------
00394 
00395 cFont *cFont::fonts[eDvbFontSize] = { NULL };
00396 
00397 void cFont::SetFont(eDvbFont Font, const char *Name, int CharHeight)
00398 {
00399   cFont *f = CreateFont(Name, constrain(CharHeight, MINFONTSIZE, MAXFONTSIZE));
00400   if (!f || !f->Height())
00401      f = new cDummyFont;
00402   delete fonts[Font];
00403   fonts[Font] = f;
00404 }
00405 
00406 const cFont *cFont::GetFont(eDvbFont Font)
00407 {
00408   if (Setup.UseSmallFont == 0 && Font == fontSml)
00409      Font = fontOsd;
00410   else if (Setup.UseSmallFont == 2)
00411      Font = fontSml;
00412   if (!fonts[Font]) {
00413      switch (Font) {
00414        case fontOsd: SetFont(Font, Setup.FontOsd, Setup.FontOsdSize); break;
00415        case fontSml: SetFont(Font, Setup.FontSml, Setup.FontSmlSize); break;
00416        case fontFix: SetFont(Font, Setup.FontFix, Setup.FontFixSize); break;
00417        default: esyslog("ERROR: unknown Font %d (%s %d)", Font, __FUNCTION__, __LINE__);
00418        }
00419      }
00420   return fonts[Font];
00421 }
00422 
00423 cFont *cFont::CreateFont(const char *Name, int CharHeight, int CharWidth)
00424 {
00425   cString fn = GetFontFileName(Name);
00426   if (*fn)
00427      return new cFreetypeFont(fn, CharHeight, CharWidth);
00428   return NULL;
00429 }
00430 
00431 bool cFont::GetAvailableFontNames(cStringList *FontNames, bool Monospaced)
00432 {
00433   if (!FontNames->Size()) {
00434      FcInit();
00435      FcObjectSet *os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL);
00436      FcPattern *pat = FcPatternCreate();
00437      FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
00438      if (Monospaced)
00439         FcPatternAddInteger(pat, FC_SPACING, FC_MONO);
00440      FcFontSet* fontset = FcFontList(NULL, pat, os);
00441      for (int i = 0; i < fontset->nfont; i++) {
00442          char *s = (char *)FcNameUnparse(fontset->fonts[i]);
00443          if (s) {
00444             // Strip i18n stuff:
00445             char *c = strchr(s, ':');
00446             if (c) {
00447                char *p = strchr(c + 1, ',');
00448                if (p)
00449                   *p = 0;
00450                }
00451             char *p = strchr(s, ',');
00452             if (p) {
00453                if (c)
00454                   memmove(p, c, strlen(c) + 1);
00455                else
00456                   *p = 0;
00457                }
00458             // Make it user presentable:
00459             s = strreplace(s, "\\", ""); // '-' is escaped
00460             s = strreplace(s, "style=", "");
00461             FontNames->Append(s); // takes ownership of s
00462             }
00463          }
00464      FcFontSetDestroy(fontset);
00465      FcPatternDestroy(pat);
00466      FcObjectSetDestroy(os);
00467      //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once
00468      FontNames->Sort();
00469      }
00470   return FontNames->Size() > 0;
00471 }
00472 
00473 cString cFont::GetFontFileName(const char *FontName)
00474 {
00475   cString FontFileName;
00476   if (FontName) {
00477      char *fn = strdup(FontName);
00478      fn = strreplace(fn, ":", ":style=");
00479      fn = strreplace(fn, "-", "\\-");
00480      FcInit();
00481      FcPattern *pat = FcNameParse((FcChar8 *)fn);
00482      FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
00483      FcConfigSubstitute(NULL, pat, FcMatchPattern);
00484      FcDefaultSubstitute(pat);
00485      FcFontSet *fontset = FcFontSort(NULL, pat, FcFalse, NULL, NULL);
00486      if (fontset) {
00487         for (int i = 0; i < fontset->nfont; i++) {
00488             FcBool scalable;
00489             FcPatternGetBool(fontset->fonts[i], FC_SCALABLE, 0, &scalable);
00490             if (scalable) {
00491                FcChar8 *s = NULL;
00492                FcPatternGetString(fontset->fonts[i], FC_FILE, 0, &s);
00493                FontFileName = (char *)s;
00494                break;
00495                }
00496             }
00497         FcFontSetDestroy(fontset);
00498         }
00499      else
00500         esyslog("ERROR: no usable font found for '%s'", FontName);
00501      FcPatternDestroy(pat);
00502      free(fn);
00503      //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once
00504      }
00505   return FontFileName;
00506 }
00507 
00508 #ifdef BIDI
00509 cString cFont::Bidi(const char *Ltr)
00510 {
00511   if (cCharSetConv::SystemCharacterTable()) { // bidi requires UTF-8
00512      fribidi_set_mirroring(true);
00513      fribidi_set_reorder_nsm(false);
00514      FriBidiCharSet fribidiCharset = FRIBIDI_CHAR_SET_UTF8;
00515      int LtrLen = strlen(Ltr);
00516      FriBidiCharType Base = FRIBIDI_TYPE_L;
00517      FriBidiChar *Logical = MALLOC(FriBidiChar, LtrLen + 1) ;
00518      int RtlLen = fribidi_charset_to_unicode(fribidiCharset, const_cast<char *>(Ltr), LtrLen, Logical);
00519      FriBidiChar *Visual = MALLOC(FriBidiChar, LtrLen + 1) ;
00520      char *Rtl = NULL;
00521      bool ok = fribidi_log2vis(Logical, RtlLen, &Base, Visual, NULL, NULL, NULL);
00522      if (ok) {
00523         fribidi_remove_bidi_marks(Visual, RtlLen, NULL, NULL, NULL);
00524         Rtl = MALLOC(char, RtlLen * 4 + 1);
00525         fribidi_unicode_to_charset(fribidiCharset, Visual, RtlLen, Rtl);
00526         }
00527      free(Logical);
00528      free(Visual);
00529      if (ok)
00530         return cString(Rtl, true);
00531      }
00532   return cString(Ltr);
00533 }
00534 #endif
00535 
00536 // --- cTextWrapper ----------------------------------------------------------
00537 
00538 cTextWrapper::cTextWrapper(void)
00539 {
00540   text = eol = NULL;
00541   lines = 0;
00542   lastLine = -1;
00543 }
00544 
00545 cTextWrapper::cTextWrapper(const char *Text, const cFont *Font, int Width)
00546 {
00547   text = NULL;
00548   Set(Text, Font, Width);
00549 }
00550 
00551 cTextWrapper::~cTextWrapper()
00552 {
00553   free(text);
00554 }
00555 
00556 void cTextWrapper::Set(const char *Text, const cFont *Font, int Width)
00557 {
00558   free(text);
00559   text = Text ? strdup(Text) : NULL;
00560   eol = NULL;
00561   lines = 0;
00562   lastLine = -1;
00563   if (!text)
00564      return;
00565   lines = 1;
00566   if (Width <= 0)
00567      return;
00568 
00569   char *Blank = NULL;
00570   char *Delim = NULL;
00571   int w = 0;
00572 
00573   stripspace(text); // strips trailing newlines
00574 
00575   for (char *p = text; *p; ) {
00576       int sl = Utf8CharLen(p);
00577       uint sym = Utf8CharGet(p, sl);
00578       if (sym == '\n') {
00579          lines++;
00580          w = 0;
00581          Blank = Delim = NULL;
00582          p++;
00583          continue;
00584          }
00585       else if (sl == 1 && isspace(sym))
00586          Blank = p;
00587       int cw = Font->Width(sym);
00588       if (w + cw > Width) {
00589          if (Blank) {
00590             *Blank = '\n';
00591             p = Blank;
00592             continue;
00593             }
00594          else {
00595             // Here's the ugly part, where we don't have any whitespace to
00596             // punch in a newline, so we need to make room for it:
00597             if (Delim)
00598                p = Delim + 1; // let's fall back to the most recent delimiter
00599             char *s = MALLOC(char, strlen(text) + 2); // The additional '\n' plus the terminating '\0'
00600             int l = p - text;
00601             strncpy(s, text, l);
00602             s[l] = '\n';
00603             strcpy(s + l + 1, p);
00604             free(text);
00605             text = s;
00606             p = text + l;
00607             continue;
00608             }
00609          }
00610       else
00611          w += cw;
00612       if (strchr("-.,:;!?_", *p)) {
00613          Delim = p;
00614          Blank = NULL;
00615          }
00616       p += sl;
00617       }
00618 }
00619 
00620 const char *cTextWrapper::Text(void)
00621 {
00622   if (eol) {
00623      *eol = '\n';
00624      eol = NULL;
00625      }
00626   return text;
00627 }
00628 
00629 const char *cTextWrapper::GetLine(int Line)
00630 {
00631   char *s = NULL;
00632   if (Line < lines) {
00633      if (eol) {
00634         *eol = '\n';
00635         if (Line == lastLine + 1)
00636            s = eol + 1;
00637         eol = NULL;
00638         }
00639      if (!s) {
00640         s = text;
00641         for (int i = 0; i < Line; i++) {
00642             s = strchr(s, '\n');
00643             if (s)
00644                s++;
00645             else
00646                break;
00647             }
00648         }
00649      if (s) {
00650         if ((eol = strchr(s, '\n')) != NULL)
00651            *eol = 0;
00652         }
00653      lastLine = Line;
00654      }
00655   return s;
00656 }