ecere/gfx/drivers/LFB; gui/EditBox: Splitting Font Rendering & Management; Italic...
[sdk] / ecere / src / gfx / fontRendering.ec
diff --git a/ecere/src/gfx/fontRendering.ec b/ecere/src/gfx/fontRendering.ec
new file mode 100644 (file)
index 0000000..4d1667b
--- /dev/null
@@ -0,0 +1,1155 @@
+namespace gfx;
+
+import "fontManagement"
+
+#if (defined(ECERE_VANILLA) || defined(ECERE_ONEDRIVER)) && defined(__WIN32__)
+#define ECERE_NOTRUETYPE
+#endif
+
+#undef __BLOCKS__
+#define uint _uint
+#define strlen _strlen
+#if !defined(ECERE_NOTRUETYPE)
+   #include <ft2build.h>
+   #include FT_FREETYPE_H
+   #include FT_TRUETYPE_TABLES_H
+   #include FT_UNPATENTED_HINTING_H
+   #define property  _property
+   #include "harfbuzz.h"
+   #undef property
+#endif
+#undef uint
+#undef strlen
+
+#if !defined(ECERE_NOTRUETYPE)
+static int utf16BufferSize = 0;
+static uint16 * utf16 = null;
+#endif
+
+#if !defined(ECERE_NOTRUETYPE)
+
+#define MAX_FONT_LINK_ENTRIES   10
+
+static HB_Script theCurrentScript;
+
+static unichar UTF16GetChar(const uint16 *string, int * nw)
+{
+   unichar ch;
+   if(HB_IsHighSurrogate(string[0]) && HB_IsLowSurrogate(string[1]))
+   {
+      ch = HB_SurrogateToUcs4(string[0], string[1]);
+      *nw = 2;
+   }
+   else
+   {
+      ch = *string;
+      *nw = 1;
+   }
+   return ch;
+}
+
+static HB_Bool hb_stringToGlyphs(HB_Font font, const uint16 * string, uint length, HB_Glyph *glyphs, uint *numGlyphs, HB_Bool rightToLeft)
+{
+   FT_Face face = ((FontEntry)font->userData).face;
+   int glyph_pos = 0;
+   int c, nw;
+
+   if (length > *numGlyphs)
+      return 0;
+
+   for (c = 0; c < length; c += nw)
+   {
+      unichar ch = UTF16GetChar(string + c, &nw);
+      glyphs[glyph_pos++] = FT_Get_Char_Index(face, ch);
+   }
+   *numGlyphs = glyph_pos;
+   return 1;
+}
+
+static void hb_getAdvances(HB_Font font, const HB_Glyph * glyphs, uint numGlyphs, HB_Fixed *advances, int flags)
+{
+   FontEntry entry = font->userData;
+   Font glFont = entry.font;
+   int c;
+   uint lastPack = 0;
+   GlyphPack pack = glFont.asciiPack;
+   int fontEntryNum;
+   for(fontEntryNum = 0; fontEntryNum < MAX_FONT_LINK_ENTRIES; fontEntryNum++)
+   {
+      if(glFont.fontEntries[fontEntryNum] == entry)
+         break;
+   }
+
+   for(c = 0; c < numGlyphs; c++)
+   {
+      Glyph * glyph;
+      uint glyphNo = glyphs[c] | 0x80000000 | (theCurrentScript << 24);
+      uint packNo = glyphNo & 0xFFFFFF80;
+      if(packNo != lastPack)
+      {
+         pack = (GlyphPack)glFont.glyphPacks.Find((uintptr)packNo);
+         if(!pack)
+         {
+            glFont.glyphPacks.Add((pack = GlyphPack { key = (uintptr)packNo }));
+            pack.Render(glFont, fontEntryNum, glFont.displaySystem);
+            pack.bitmap.alphaBlend = true;
+         }
+         lastPack = packNo;
+      }
+      glyph = &pack.glyphs[glyphNo & 0x7F];
+      advances[c] = glyph->ax;
+   }
+}
+
+static HB_Bool hb_canRender(HB_Font font, const uint16 * string, uint length)
+{
+   FT_Face face = ((FontEntry)font->userData).face;
+   int c, nw;
+
+   for (c = 0; c < length; c += nw)
+   {
+      unichar ch = UTF16GetChar(string + c, &nw);
+      if(!FT_Get_Char_Index(face, ch))
+         return 0;
+   }
+   return 1;
+}
+
+static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length)
+{
+    FT_Face face = (FT_Face)font;
+    FT_ULong ftlen = *length;
+    FT_Error error = 0;
+
+    if (!FT_IS_SFNT(face))
+        return HB_Err_Invalid_Argument;
+
+    error = FT_Load_Sfnt_Table(face, tableTag, 0, buffer, &ftlen);
+    *length = (uint)ftlen;
+    return (HB_Error)error;
+}
+
+static HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints)
+{
+    HB_Error error = HB_Err_Ok;
+    FT_Face face = (FT_Face)font->userData;
+
+    int load_flags = (flags & HB_ShaperFlag_UseDesignMetrics) ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT;
+
+    if ((error = (HB_Error)FT_Load_Glyph(face, glyph, load_flags)))
+        return error;
+
+    if (face->glyph->format != ft_glyph_format_outline)
+        return (HB_Error)HB_Err_Invalid_SubTable;
+
+    *nPoints = face->glyph->outline.n_points;
+    if (!(*nPoints))
+        return HB_Err_Ok;
+
+    if (point > *nPoints)
+        return (HB_Error)HB_Err_Invalid_SubTable;
+
+    *xpos = (int)face->glyph->outline.points[point].x;
+    *ypos = (int)face->glyph->outline.points[point].y;
+
+    return HB_Err_Ok;
+}
+
+static void hb_getGlyphMetrics(HB_Font font, HB_Glyph theGlyph, HB_GlyphMetrics *metrics)
+{
+   FontEntry entry = font->userData;
+   Font glFont = entry.font;
+   uint lastPack = 0;
+   GlyphPack pack = glFont.asciiPack;
+   int fontEntryNum;
+   for(fontEntryNum = 0; fontEntryNum < MAX_FONT_LINK_ENTRIES; fontEntryNum++)
+   {
+      if(glFont.fontEntries[fontEntryNum] == entry)
+         break;
+   }
+   {
+      Glyph * glyph;
+      uint glyphNo = theGlyph | 0x80000000 | (theCurrentScript << 24);
+      uint packNo = glyphNo & 0xFFFFFF80;
+      if(packNo != lastPack)
+      {
+         pack = (GlyphPack)glFont.glyphPacks.Find((uintptr)packNo);
+         if(!pack)
+         {
+            pack = { key = (uintptr)packNo };
+            glFont.glyphPacks.Add(pack);
+            pack.Render(glFont, fontEntryNum, glFont.displaySystem);
+            pack.bitmap.alphaBlend = true;
+         }
+         lastPack = packNo;
+      }
+      glyph = &pack.glyphs[glyphNo & 0x7F];
+
+      metrics->x = glyph->ax;
+      metrics->y = 0;
+      metrics->width = glyph->w;
+      metrics->height = glyph->h;
+      metrics->xOffset = glyph->bx;
+      metrics->yOffset = glyph->by;
+   }
+}
+
+static HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric)
+{
+   FontEntry entry = font->userData;
+   FT_Face face = entry.face;
+
+   // Note that we aren't scanning the VDMX table which we probably would in
+   // an ideal world.
+   if(metric == HB_FontAscent)
+      return face->ascender;
+   return 0;
+}
+
+static HB_FontClass hb_fontClass =
+{
+   hb_stringToGlyphs, hb_getAdvances, hb_canRender,
+   hb_getPointInOutline, hb_getGlyphMetrics, hb_getFontMetric
+};
+
+static uint FT_stream_load(FT_Stream stream, long offset, byte * buffer, long count)
+{
+    File f = stream->descriptor.pointer;
+    f.Seek((int)offset, start);
+    return count ? f.Read(buffer, 1, (uint)count) : 0;
+}
+
+static void FT_stream_close(FT_Stream stream)
+{
+   File f = stream->descriptor.pointer;
+   delete f;
+   delete stream;
+}
+
+static FT_Library ftLibrary;
+static int numFonts;
+#undef CompareString
+static BinaryTree loadedFonts
+{
+   CompareKey = (void *)BinaryTree::CompareString
+};
+
+class FontEntry : BTNode
+{
+   FT_Face face;
+   HB_FontRec hbFont;
+   HB_Face hbFace;
+
+   int used;
+   byte * buffer;
+
+   //If we don't save the FT_Stream before sacrificing it to FreeType, the garbage collector (if one is used) will destroy it prematurely
+   FT_Stream stream;
+   Font font;
+   float scale;
+
+   ~FontEntry()
+   {
+      delete (char *)key;
+      delete buffer;
+      if(hbFace)
+         HB_FreeFace(hbFace);
+      if(face)
+      {
+         FT_Done_Face(face);
+         numFonts--;
+         if(!numFonts)
+         {
+            FT_Done_FreeType(ftLibrary);
+            ftLibrary = null;
+         }
+      }
+   }
+
+   FontEntry ::Load(FaceInfo info)
+   {
+      FontEntry fontEntry = (FontEntry)loadedFonts.FindString(info.fileName);
+      if(!fontEntry)
+      {
+         File file = FileOpen/*Buffered*/(info.fileName, read);
+         if(file)
+         {
+            FileSize fileSize = file.GetSize();
+            FT_Open_Args args = { 0 };
+            FT_Parameter param = { FT_PARAM_TAG_UNPATENTED_HINTING };
+            FT_Stream stream = new0 FT_StreamRec[1];
+
+            if(!ftLibrary)
+               FT_Init_FreeType( &ftLibrary );
+
+            fontEntry = FontEntry { key = (uintptr)CopyString(info.fileName) };
+            fontEntry.stream = stream;
+
+            /*
+            fontEntry.buffer = new byte[fileSize];
+            file.Read(fontEntry.buffer, 1, fileSize);
+            */
+
+            //args.num_params = 1;
+            args.params = &param;
+
+            stream->size = fileSize;
+            stream->descriptor.pointer = file;
+            stream->read = FT_stream_load;
+            stream->close = FT_stream_close;
+
+            args.flags = /*FT_OPEN_PATHNAME|*//*FT_OPEN_MEMORY|*/FT_OPEN_STREAM/*|FT_OPEN_PARAMS*/;
+            args.stream = stream;
+            //args.pathname = fileName;
+            //args.memory_base = fontEntry.buffer;
+            //args.memory_size = fileSize;
+
+            // printf("Opening: %s\n", fileName);
+            FT_Open_Face( ftLibrary, &args, info.fontID, &fontEntry.face );
+
+            // delete file;
+            if(fontEntry.face)
+            {
+               fontEntry.hbFace = HB_NewFace(fontEntry.face, hb_getSFntTable);
+               fontEntry.hbFont.klass = &hb_fontClass;
+               fontEntry.hbFont.userData = fontEntry; //.face;
+
+               numFonts++;
+               loadedFonts.Add(fontEntry);
+            }
+            else
+            {
+               delete fontEntry;
+               // printf("Error opening font %s\n", fileName);
+            }
+         }
+      }
+      return fontEntry;
+   }
+}
+
+static float FaceSetCharSize(FT_Face face, float size)
+{
+   float scale = 1;
+   if(FT_Set_Char_Size(face, (int)(size * 64), (int)(size * 64), 96, 96))
+   {
+      if(face->num_fixed_sizes)
+      {
+         int c;
+         int bestDiff = MAXINT, best = 0;
+         FT_Bitmap_Size * sizes = face->available_sizes;
+         int wishedHeight = (int)(size * 96 / 72);
+         for(c = 0; c < face->num_fixed_sizes; c++)
+         {
+            int diff = abs(sizes[c].height - wishedHeight);
+            if(diff < bestDiff)
+            {
+               best = c;
+               bestDiff = diff;
+            }
+         }
+         FT_Set_Pixel_Sizes(face, sizes[best].width, sizes[best].height);
+
+         if(!face->ascender)
+            face->ascender = sizes[best].height;
+         scale = (float)wishedHeight / sizes[best].height;
+      }
+   }
+   return scale;
+}
+
+#endif
+
+struct Glyph
+{
+   int ax, ay;
+   int x, y;
+   int w, h;
+   int left, top;
+   int bx, by;
+   int glyphNo;
+   float scale;
+};
+
+class GlyphPack : BTNode
+{
+   Glyph glyphs[256];
+   Bitmap bitmap { };
+   int cellWidth, cellHeight;
+
+   void Render(Font font, int startFontEntry, DisplaySystem displaySystem)
+   {
+#if !defined(ECERE_NOTRUETYPE)
+      unichar c;
+      int maxWidth, maxHeight;
+      int cellWidth, cellHeight;
+      int width, height;
+      FontEntry fontEntry = null;
+      FT_Face faces[128];
+      float scales[128];
+      bool isGlyph = ((uint)key & 0x80000000) != 0;
+      //int curScript = ((uint)key & 0x7F000000) >> 24;
+      unichar testChar = 0;
+      /*
+      if(isGlyph)
+      {
+         switch(curScript)
+         {
+            case HB_Script_Arabic:
+               testChar = 0x621;
+               // printf("\nRendering arabic in %s (%d)\n", faceName, key & 0xFFFFFF);
+               break;
+            case HB_Script_Devanagari:
+               testChar = 0x905;
+               break;
+            case 60: testChar = 'あ'; break;
+            case 61: testChar = 0x3400; break;
+         }
+      }
+      */
+      /*
+      FT_GlyphSlot slot;
+      FT_Matrix matrix;
+      FT_Vector pen = { 0, 0 };
+      if(fakeItalic)
+      {
+         matrix.xx = (FT_Fixed)( 1.0 * 0x10000L );
+         matrix.xy = (FT_Fixed)( 0.3 * 0x10000L );
+         matrix.yx = (FT_Fixed)( 0.0 * 0x10000L );
+         matrix.yy = (FT_Fixed)( 1.0 * 0x10000L );
+         FT_Set_Transform( fontEntry.face, &matrix, &pen );
+      }
+      FT_Set_Char_Size( fontEntry.face, (int)(size * 64), (int)(size * 64), 96, 96);
+      */
+
+      maxWidth = 0;
+      maxHeight = 0;
+
+      for(c = 0; c < MAX_FONT_LINK_ENTRIES; c++)
+      {
+         fontEntry = font.fontEntries[c];
+         if(fontEntry)
+         {
+            FT_Matrix matrix;
+            FT_Vector pen = { 0, 0 };
+
+            if(font.fakeItalic)
+            {
+               matrix.xx = (FT_Fixed)( 1.0 * 0x10000L );
+               matrix.xy = (FT_Fixed)( 0.3 * 0x10000L );
+               matrix.yx = (FT_Fixed)( 0.0 * 0x10000L );
+               matrix.yy = (FT_Fixed)( 1.0 * 0x10000L );
+               FT_Set_Transform(fontEntry.face, &matrix, &pen);
+            }
+            //FT_Set_Char_Size(fontEntry.face, (int)(size * 64), (int)(size * 64), 96, 96);
+            fontEntry.scale = FaceSetCharSize(fontEntry.face, font.size);
+            if(!font.scale)
+               font.scale = fontEntry.scale;
+            if(!c)
+            {
+               if(!fontEntry.face->units_per_EM)
+               {
+                  font.ascent = (int)((double)fontEntry.face->ascender);
+                  font.descent = (int)((double)fontEntry.face->descender);
+               }
+               else
+               {
+                  font.ascent = (int)((double)fontEntry.face->ascender * fontEntry.face->size->metrics.y_ppem / fontEntry.face->units_per_EM);
+                  font.descent = (int)((double)fontEntry.face->descender * fontEntry.face->size->metrics.y_ppem / fontEntry.face->units_per_EM);
+               }
+            }
+
+            fontEntry.hbFont.x_ppem  = fontEntry.face->size->metrics.x_ppem;
+            fontEntry.hbFont.y_ppem  = fontEntry.face->size->metrics.y_ppem;
+            fontEntry.hbFont.x_scale = (int)fontEntry.face->size->metrics.x_scale;
+            fontEntry.hbFont.y_scale = (int)fontEntry.face->size->metrics.y_scale;
+         }
+      }
+
+      fontEntry = null;
+      for(c = 0; c < 128; c++)
+      {
+         int entry = 0;
+         if(isGlyph)
+         {
+            uint glyph = ((uint)key | c) & 0xFFFFFF;
+            for(entry = startFontEntry; entry < MAX_FONT_LINK_ENTRIES; entry++)
+            {
+               fontEntry = font.fontEntries[entry];
+               if(fontEntry && (FT_Get_Char_Index(fontEntry.face, testChar) || !testChar || entry == MAX_FONT_LINK_ENTRIES-1 || !font.fontEntries[entry+1]))
+               {
+                  if(!FT_Load_Glyph(fontEntry.face, glyph, FT_LOAD_DEFAULT /*FT_LOAD_NO_HINTING*/) || entry == MAX_FONT_LINK_ENTRIES-1 || !font.fontEntries[entry+1])
+                  {
+                     //printf("%s: Accepted entry %d ", faceName, entry);
+                     break;
+                  }
+               }
+            }
+         }
+         else
+         {
+            for(entry = startFontEntry; entry < MAX_FONT_LINK_ENTRIES; entry++)
+            {
+               uint glyph;
+               fontEntry = font.fontEntries[entry];
+               if(fontEntry && ((glyph = FT_Get_Char_Index(fontEntry.face, ((uint)key | c) & 0xFFFFFF)) || entry == MAX_FONT_LINK_ENTRIES-1 || !font.fontEntries[entry+1]))
+               {
+                  if(!FT_Load_Glyph(fontEntry.face, glyph, FT_LOAD_DEFAULT /*FT_LOAD_NO_HINTING*/) || entry == MAX_FONT_LINK_ENTRIES-1 || !font.fontEntries[entry+1])
+                     break;
+               }
+            }
+         }
+         scales[c] = fontEntry ? fontEntry.scale : 0;
+         faces[c] = fontEntry ? fontEntry.face : null;
+         if(fontEntry)
+         {
+            maxWidth = Max(maxWidth, ((faces[c]->glyph->metrics.width + 64 + (64 - (faces[c]->glyph->metrics.width & 0x3F))) >> 6));
+            maxHeight = Max(maxHeight, ((faces[c]->glyph->metrics.height + 64 + (64 - (faces[c]->glyph->metrics.height & 0x3F))) >> 6));
+            //maxHeight = Max(maxHeight, ((faces[c]->glyph->metrics.height) >> 6));
+         }
+      }
+      this.cellWidth = cellWidth = maxWidth;
+      this.cellHeight = cellHeight = maxHeight;
+
+      width = maxWidth * 16;
+      height = maxHeight * 8;
+
+      if(true)
+      {
+         width = pow2i(width);
+         height = pow2i(height);
+      }
+
+      if(bitmap.Allocate(null, width, height, 0, pixelFormatAlpha, false /*true*/))
+      {
+         Bitmap bitmap = this.bitmap;
+
+         bitmap.transparent = true;
+
+         for(c = 0; c < 128; c++)
+         {
+            FT_Int i, j, p, q;
+            FT_Int xMax, yMax;
+            int sx = (c % 16) * cellWidth;
+            int sy = (c / 16) * cellHeight;
+            int x, y;
+            byte * picture = (byte *)bitmap.picture;
+            Glyph * glyph = &glyphs[c];
+            FT_GlyphSlot slot = null;
+            int glyphNo = isGlyph ? (((uint)key | c) & 0x00FFFFFF) : (faces[c] ? FT_Get_Char_Index(faces[c], (uint)key | c) : 0);
+            if(faces[c])
+            {
+               double em_size = 1.0 * faces[c]->units_per_EM;
+               //double x_scale = faces[c]->size->metrics.x_ppem / em_size;
+               double y_scale = em_size ? (faces[c]->size->metrics.y_ppem / em_size) : 1;
+               double ascender = faces[c]->ascender * y_scale;
+               slot = faces[c]->glyph;
+
+               FT_Load_Glyph(faces[c], glyphNo, /*FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT*/ FT_LOAD_DEFAULT /*FT_LOAD_NO_HINTING*/); // FT_LOAD_RENDER // FT_LOAD_NO_HINTING
+
+               FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
+
+               x = sx;
+               y = sy;
+               //printf("%d, %d\n", maxHeight, faces[c]->size->metrics.height >> 6);
+
+               glyph->left = slot->bitmap_left;
+               // glyph->top = ((64 + (64 - faces[c]->glyph->metrics.height & 0x3F)) >> 6) + (int)(ascender - slot->bitmap_top) + height - (faces[c]->size->metrics.height >> 6);
+               // glyph->top = (int)(ascender - slot->bitmap_top) + 2 * (height - maxHeight);
+               //glyph->top = (int)(ascender - slot->bitmap_top) + 2 * ((((faces[c]->size->metrics.height + 64 + (64 - faces[c]->size->metrics.height & 0x3F)) >> 6)) - height);
+               //glyph->top = (int)(ascender - slot->bitmap_top) + (height - (faces[c]->size->metrics.height >> 6));
+
+               //glyph->top = (int)(ascender + (height *64 - /*faces[0]->size->metrics.height - */faces[c]->size->metrics.height) / 64.0  + 0.5) - slot->bitmap_top;
+               //glyph->top = (int)(ascender + (height *64 - /*faces[0]->size->metrics.height - */faces[c]->size->metrics.height) / 64.0  + 0.5) - slot->bitmap_top;
+
+               //glyph->top = (int)((ascender - slot->bitmap_top) + (height * 64 - maxHeight * 64 + faces[c]->glyph->metrics.height - faces[c]->glyph->metrics.height) / 64);
+
+               //glyph->top = (int)(ascender - slot->bitmap_top); // + ((faces[c]->size->metrics.height >> 6) - (faces[0]->size->metrics.height >> 6)) + (height - (faces[c]->size->metrics.height >> 6));
+               //glyph->top = (int)(ascender - slot->bitmap_top); // + ((faces[c]->size->metrics.height >> 6) - (faces[0]->size->metrics.height >> 6)) + (height - (faces[c]->size->metrics.height >> 6));
+
+               //glyph->top = (int)(ascender - slot->bitmap_top);// + (height - maxHeight);
+               glyph->top = (int)(ascender - slot->bitmap_top) + (int)(font.height - (faces[c]->size->metrics.height >> 6)) / 2;
+
+               // printf("[char: %d] mode: %d, width: %d, height: %d, pitch: %d\n", key + c, slot->bitmap.pixel_mode, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
+               xMax = x + slot->bitmap.width;
+               yMax = y + slot->bitmap.rows;
+
+               {
+                  int total = 0;
+                  int numPixels = 0;
+                  //int max;
+                  if(slot->bitmap.pixel_mode != FT_PIXEL_MODE_MONO)
+                  {
+                     for(j = y, q = 0; j<yMax; j++)
+                     {
+                        for(p = 0, i = x; i<xMax; i++)
+                        {
+                           byte value = ((byte *)slot->bitmap.buffer)[q + p++];
+                           if(value > 32)
+                           {
+                              total += value;
+                              numPixels++;
+                           }
+                        }
+                        q += slot->bitmap.pitch;
+                     }
+                  }
+                  //max = numPixels ? (total / numPixels) : 1;
+
+                  for(j = y, q = 0; j<yMax; j++)
+                  {
+                     int bit = 0x80;
+                     for(p = 0, i = x; i<xMax; i++)
+                     {
+                        if(slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
+                        {
+                           picture[j * bitmap.stride + i] = (slot->bitmap.buffer[q + p] & bit) ? 255 : 0;
+                           bit >>= 1;
+                           if(!bit) { bit = 0x80; p++; }
+                        }
+                        else
+                        {
+                           byte value = ((byte *)slot->bitmap.buffer)[q + p++];
+                           picture[j * bitmap.stride + i] = /*(max < 192) ? Min(255, value * 192/max) :*/ value;
+                        }
+                     }
+                     q += slot->bitmap.pitch;
+                  }
+               }
+            }
+            glyph->x = sx;
+            glyph->y = sy;
+            if(slot)
+            {
+               glyph->w = slot->bitmap.width;
+               glyph->h = slot->bitmap.rows;
+               glyph->ax = (int)slot->advance.x;
+               glyph->ay = (int)(slot->advance.y + (64 - slot->advance.y % 64));
+            }
+            glyph->glyphNo = glyphNo;
+            if(faces[c])
+            {
+               glyph->bx = (int)faces[c]->glyph->metrics.horiBearingX;
+               glyph->by = (int)faces[c]->glyph->metrics.horiBearingY;
+            }
+            glyph->scale = scales[c];
+         }
+         #if 0
+         {
+            int c;
+            char fileName[256];
+            static int fid = 0;
+            for(c = 0; c<256; c++)
+               bitmap.palette[c] = ColorAlpha { 255, { (byte)c,(byte)c,(byte)c } };
+            bitmap.pixelFormat = pixelFormat8;
+
+            /*
+            //strcpy(fileName, faceName);
+            if(flags)
+               strcat(fileName, "Bold");
+            */
+            sprintf(fileName, "font%d", fid++);
+            ChangeExtension(fileName, "pcx", fileName);
+
+            bitmap.Save(fileName, null, 0);
+            bitmap.pixelFormat = pixelFormatAlpha;
+         }
+         #endif
+
+         if(displaySystem && displaySystem.pixelFormat != pixelFormat4) // TODO: Add none PixelFormat
+         {
+            displaySystem.Lock();
+#if defined(__WIN32__)
+            // Is this check still required?
+            if(displaySystem.driver == class(OpenGLDisplayDriver)
+
+#if !defined(_GLES) && !defined(ECERE_STATIC)
+            || displaySystem.driver == class(Direct3D8DisplayDriver)
+            || displaySystem.driver == class(Direct3D9DisplayDriver)
+#endif
+
+               )
+#endif
+               bitmap.MakeDD(displaySystem);
+            displaySystem.Unlock();
+         }
+      }
+#endif
+   }
+}
+
+#if !defined(ECERE_NOTRUETYPE)
+static HB_ShaperItem shaper_item;
+
+static uint * shaping(FontEntry entry, uint16 * string, int len, HB_Script script, int *numGlyphs, bool * rightToLeft)
+{
+   static uint maxGlyphs = 0;
+   HB_Glyph * glyphs = shaper_item.glyphs;
+
+   shaper_item.kerning_applied = 0;
+   shaper_item.string = string;
+   shaper_item.stringLength = len;
+   shaper_item.item.script = script;
+   shaper_item.item.pos = 0;
+   shaper_item.item.length = shaper_item.stringLength;
+   if(script == HB_Script_Arabic || script == HB_Script_Hebrew || script == HB_Script_Thaana || script == HB_Script_Syriac)
+      shaper_item.item.bidiLevel = 1;
+   else
+      shaper_item.item.bidiLevel = 0;
+   shaper_item.shaperFlags = 0;
+   shaper_item.font = &entry.hbFont;
+   shaper_item.face = entry.hbFace;
+   shaper_item.num_glyphs = shaper_item.item.length;
+   shaper_item.glyphIndicesPresent = 0;
+   shaper_item.initialGlyphCount = 0;
+   shaper_item.num_glyphs = 0;
+   shaper_item.glyphs = null;
+
+   while(!HB_ShapeItem(&shaper_item))
+   {
+      if(shaper_item.num_glyphs > maxGlyphs)
+      {
+         maxGlyphs = shaper_item.num_glyphs;
+         glyphs = shaper_item.glyphs = renew0 glyphs HB_Glyph[maxGlyphs];
+         shaper_item.attributes   = renew0 shaper_item.attributes HB_GlyphAttributes[maxGlyphs];
+         shaper_item.advances     = renew0 shaper_item.advances HB_Fixed[maxGlyphs];
+         shaper_item.offsets      = renew0 shaper_item.offsets HB_FixedPoint[maxGlyphs];
+         shaper_item.log_clusters = renew0 shaper_item.log_clusters unsigned short[maxGlyphs];
+      }
+      else
+      {
+         shaper_item.glyphs = glyphs;
+         shaper_item.num_glyphs = maxGlyphs;
+      }
+  }
+
+   *numGlyphs = shaper_item.num_glyphs;
+   *rightToLeft = (bool)(shaper_item.item.bidiLevel % 2);
+   return shaper_item.glyphs;
+}
+
+/*
+   delete shaper_item.glyphs;
+   delete shaper_item.attributes;
+   delete shaper_item.advances;
+   delete shaper_item.offsets;
+   delete shaper_item.log_clusters;
+*/
+#endif
+
+public class Font : struct
+{
+   char faceName[512];
+   FontFlags flags;
+   float size;
+   int ascent, descent;
+   float scale;
+
+   BinaryTree glyphPacks { };
+   GlyphPack asciiPack { };
+   bool fakeItalic;
+   int height;
+   DisplaySystem displaySystem;
+   int numEntries;
+#if !defined(ECERE_NOTRUETYPE)
+   FontEntry fontEntries[MAX_FONT_LINK_ENTRIES];
+#endif
+
+   ~Font()
+   {
+      int entry;
+
+#if !defined(ECERE_NOTRUETYPE)
+      GlyphPack pack;
+      while((pack = (GlyphPack)glyphPacks.root))
+      {
+         glyphPacks.Remove(pack);
+         delete pack;
+      }
+#endif
+
+#if !defined(ECERE_NOTRUETYPE)
+      for(entry = 0; entry<MAX_FONT_LINK_ENTRIES; entry++)
+      {
+         FontEntry fontEntry = fontEntries[entry];
+         if(fontEntry)
+         {
+            fontEntry.used--;
+            if(!fontEntry.used)
+            {
+               loadedFonts.Remove(fontEntry);
+               delete fontEntry;
+            }
+         }
+      }
+#endif
+   }
+   public property int ascent
+   {
+      get { return (int)(this ? ascent * scale : 0); }
+   }
+   public property int descent
+   {
+      get { return (int)(this ? descent * scale : 0); }
+   }
+
+   void Setup(DisplaySystem displaySystem, const String faceName, float size, FontFlags flags)
+   {
+      strcpy(this.faceName, faceName);
+      this.flags = flags;
+      this.displaySystem = displaySystem;
+      this.size = size;
+   }
+
+   bool LoadEntry(FaceInfo info)
+   {
+      bool result = false;
+      if(numEntries < MAX_FONT_LINK_ENTRIES)
+      {
+         FontEntry fontEntry = FontEntry::Load(info);
+         if(fontEntry)
+         {
+            if(!numEntries)
+            {
+               FT_Matrix matrix;
+               FT_Vector pen = { 0, 0 };
+               if(fakeItalic)
+               {
+                  matrix.xx = (FT_Fixed)( 1.0 * 0x10000L );
+                  matrix.xy = (FT_Fixed)( 0.3 * 0x10000L );
+                  matrix.yx = (FT_Fixed)( 0.0 * 0x10000L );
+                  matrix.yy = (FT_Fixed)( 1.0 * 0x10000L );
+               }
+               else
+               {
+                  matrix.xx = (FT_Fixed)( 1.0 * 0x10000L );
+                  matrix.xy = (FT_Fixed)( 0.0 * 0x10000L );
+                  matrix.yx = (FT_Fixed)( 0.0 * 0x10000L );
+                  matrix.yy = (FT_Fixed)( 1.0 * 0x10000L );
+               }
+               FT_Set_Transform(fontEntry.face, &matrix, &pen );
+               FaceSetCharSize(fontEntry.face, size);
+               height = (int)((fontEntry.face->size->metrics.height) >> 6); //* y_scale;
+               // printf("Font height is %d\n", height);
+               this.fakeItalic = info.fakeItalic;
+            }
+            fontEntries[numEntries++] = fontEntry;
+            fontEntry.used++;
+
+            result = true;
+         }
+      }
+      return result;
+   }
+
+   Font ::Load(DisplaySystem displaySystem, const char * faceName, float size, FontFlags flags)
+   {
+      Font result = null;
+      Array<FaceInfo> infos = ResolveFont(faceName, size, flags);
+      if(infos)
+      {
+         Font font { };
+         bool success = false;
+         font.Setup(displaySystem, faceName, size, flags);
+         for(f : infos)
+            success |= font.LoadEntry(f);
+         if(success)
+         {
+            font.asciiPack.Render(font, 0, displaySystem);
+            result = font;
+         }
+         else
+            delete font;
+         infos.Free();
+         delete infos;
+      }
+      return result;
+   }
+
+   void ProcessString(DisplaySystem displaySystem, const byte * text, int len,
+                      void (* callback)(Surface surface, Display display, int x, int y, Glyph glyph, Bitmap bitmap),
+                      Surface surface, Display display, int * x, int y, int prevGlyph, int * rPrevGlyph, int * advance)
+   {
+#if !defined(ECERE_NOTRUETYPE)
+      if(this && fontEntries[0])
+      {
+         int previousGlyph = prevGlyph;
+         FT_Face previousFace = 0;
+         int c, nb, glyphIndex = 0;
+         unichar lastPack = 0;
+         GlyphPack pack = asciiPack;
+         int wc = 0;
+         uint * glyphs = null;
+         int numGlyphs = 0;
+         bool rightToLeft = false;
+         int fontEntryNum = 0;
+         int glyphScript = 0;
+         FontEntry curFontEntry;
+         Glyph * lastGlyph = null;
+         int lastAX = 0;
+
+         pack.bitmap.alphaBlend = true;
+
+         for(c = 0; c < len || (numGlyphs && (rightToLeft ? (glyphIndex >= 0) : (glyphIndex < numGlyphs)));)
+         {
+            uint glyphNo = 0;
+            uint packNo;
+            if(numGlyphs && (rightToLeft ? (glyphIndex >= 0) : (glyphIndex < numGlyphs)))
+            {
+               glyphNo = glyphs[glyphIndex] | 0x80000000 | (glyphScript << 24);
+               rightToLeft ? glyphIndex-- : glyphIndex++;
+            }
+            else
+            {
+               HB_Script curScript = HB_Script_Common;
+               const byte * scriptStart = text + c;
+               //unichar nonASCIIch = 0;
+               unichar ch;
+               unichar ahead = 0;
+               unichar testChar = 0;
+               const String testLang = null;
+
+               while(true)
+               {
+                  HB_Script script = HB_Script_Common;
+                  ch = UTF8GetChar((const char *)text + c, &nb);
+                  //if(ch > 127) nonASCIIch = ch;
+                  if(!nb) break;
+                  if(ch == 32 && curScript)
+                  {
+                     if(ahead)
+                        script = curScript;
+                     else
+                     {
+                        int a;
+                        for(a = c + 1; a < c + len; a++)
+                        {
+                           if(text[a] != 32)
+                              break;
+                        }
+                        if(a < c + len)
+                        {
+                           int nb;
+                           unichar ahead = UTF8GetChar((const char *)text + a, &nb);
+                           if((ahead >= 0x590 && ahead <= 0x7C0) || (ahead >= 0xFB1D && ahead <= 0xFB4F) || (ahead >= 0xFB50 && ahead <= 0xFDFF))
+                              script = curScript;
+                        }
+                        else
+                           script = curScript;
+                     }
+                  }
+                  else if(ch < 0x370)
+                     script = HB_Script_Common;
+                  else if(ch <= 0x11FF)
+                  {
+                     switch(ch & 0xFF00)
+                     {
+                        case 0x300: script = HB_Script_Greek; break;
+                        case 0x400: script = HB_Script_Cyrillic; break;
+                        case 0x500: script = (ch < 0x530) ? HB_Script_Cyrillic : ((ch < 0x590) ? HB_Script_Armenian : HB_Script_Hebrew); break;
+                        case 0x600: script = HB_Script_Arabic; break;
+                        case 0x700: script = (ch < 0x750) ? HB_Script_Syriac : ((ch < 0x780) ? HB_Script_Arabic : ((ch < 0x7C0) ? HB_Script_Thaana : HB_Script_Common)); break;
+                        case 0x800: script = HB_Script_Common; break;   // NO CHARACTERS ASSIGNED BETWEEN 0x7C0 and 0x8FF?
+                        case 0x900: script = (ch < 0x980) ? HB_Script_Devanagari : HB_Script_Bengali; break;
+                        case 0xA00: script = (ch < 0xA80) ? HB_Script_Gurmukhi : HB_Script_Gujarati; break;
+                        case 0xB00: script = (ch < 0xB80) ? HB_Script_Oriya : HB_Script_Tamil; break;
+                        case 0xC00: script = (ch < 0xC80) ? HB_Script_Telugu : HB_Script_Kannada; break;
+                        case 0xD00: script = (ch < 0xD80) ? HB_Script_Malayalam : HB_Script_Sinhala; break;
+                        case 0xE00: script = (ch < 0xE80) ? HB_Script_Thai : HB_Script_Lao; break;
+                        case 0xF00: script = HB_Script_Tibetan; break;
+                        case 0x1000: script = (ch < 0x10A0) ? HB_Script_Myanmar : HB_Script_Georgian; break;
+                        case 0x1100: script = HB_Script_Hangul; break;
+                     }
+                  }
+                  else if(ch >= 0x1F00 && ch <= 0x1FFF) script = HB_Script_Greek;
+                  else if((ch >= 0x2D00 && ch <= 0x2D2F) || (ch >= 0x3130 && ch <= 0x318F) || (ch >= 0xAC00 && ch <= 0xD7AF) || (ch >= 0xFFA0 && ch <= 0xFFDC))
+                     script = HB_Script_Hangul;
+                  else if(ch >= 0x1680 && ch <= 0x169F) script = HB_Script_Ogham;
+                  else if(ch >= 0x16A0 && ch <= 0x16FF) script = HB_Script_Runic;
+                  else if((ch >= 0x1780 && ch <= 0x17FF) || (ch >= 0x19E0 && ch <= 0x19FF)) script = HB_Script_Khmer;
+                  else if(ch >= 0x3040 && ch <= 0x309F) script = 60;
+                  else if(ch >= 0x3400 && ch <= 0x9FBF) script = 61;
+                  //else if(ch >= 0x4E00 && ch <= 0x9FBF) script = 61;
+                  else if(ch >= 0xFB13 && ch <= 0xFB17) script = HB_Script_Armenian;
+                  else if(ch >= 0xFB1D && ch <= 0xFB4F) script = HB_Script_Hebrew;
+                  else if(ch >= 0xFB50 && ch <= 0xFDFF) script = HB_Script_Arabic;
+                  if(curScript)
+                  {
+                     if(!script || (script != curScript))
+                        break;
+                     c += nb;
+                     if(c >= len)
+                        break;
+                  }
+                  else
+                  {
+                     if(!script || script > HB_ScriptCount) { c += nb; if(script > HB_ScriptCount) curScript = script; break; }
+                     if(!script) { c += nb; break; }
+                     curScript = script;
+                  }
+               }
+               if(!nb) break;
+               fontEntryNum = 0;
+
+               if(curScript == HB_Script_Common || curScript > HB_ScriptCount)
+               {
+                  rightToLeft = false;
+                  glyphNo = ch;
+                  theCurrentScript = 0;
+               }
+               else
+               {
+                  int len = c - (int)(scriptStart - text);
+                  int max = len * 2 + 1;
+                  if(max > utf16BufferSize)
+                  {
+                     utf16 = renew utf16 uint16[max];
+                     utf16BufferSize = max;
+                  }
+                  wc = UTF8toUTF16BufferLen((const char *)scriptStart, utf16, max, len);
+                  theCurrentScript = glyphScript = curScript;
+               }
+               switch(curScript)
+               {
+                  case HB_Script_Arabic:        testChar = 0x621; /*testLang = "ar"; */
+                     //printf("Arabic ");
+                     break;
+                  case HB_Script_Devanagari:    testChar = 0x905;
+                     testLang = "sa";
+                     //printf("Devanagari ");
+                     break;
+                  case HB_Script_Hebrew:        testChar = 0x05EA /*'ת'*/; /*testLang = "he"; */
+                     //printf("Hebrew ");
+                     break;
+                  default:
+                     testChar = (ch == '\t') ? ' ' : ch;
+                  /*
+                  case 60: testChar = 'あ'; break;
+                  case 61: testChar = 0x3400; break; //'愛'; break;
+                  */
+               }
+
+               if(testChar)
+               {
+                  // printf("Testing for char %x\n", testChar);
+                  for(fontEntryNum = 0; fontEntryNum<MAX_FONT_LINK_ENTRIES; fontEntryNum++)
+                  {
+                     if(fontEntries[fontEntryNum] && FT_Get_Char_Index(fontEntries[fontEntryNum].face, testChar))
+                        break;
+                     /*if(fontEntries[fontEntryNum])
+                        printf("Not found in %s\n", (char *)fontEntries[fontEntryNum].key);*/
+                  }
+               }
+
+               if(fontEntryNum == MAX_FONT_LINK_ENTRIES)
+               {
+                  FaceInfo info;
+                  // Do we still have room to add an entry?
+                  for(fontEntryNum = 0; fontEntryNum<MAX_FONT_LINK_ENTRIES; fontEntryNum++)
+                     if(!fontEntries[fontEntryNum])
+                        break;
+                  if(fontEntryNum == MAX_FONT_LINK_ENTRIES)
+                     continue;
+
+                  if((info = ResolveCharFont(faceName, size, flags, testLang, testChar)))
+                  {
+                     FontEntry fontEntry = FontEntry::Load(info);
+                     if(fontEntry)
+                     {
+                        FaceSetCharSize(fontEntry.face, size);
+                        fontEntries[fontEntryNum] = fontEntry;
+                        fontEntry.used++;
+                     }
+                     delete info;
+                  }
+
+                  if(!fontEntries[fontEntryNum])
+                     continue;
+               }
+               if(curScript > HB_ScriptCount) curScript = HB_Script_Common;
+               if(curScript != HB_Script_Common && curScript < HB_ScriptCount)
+               {
+                  fontEntries[fontEntryNum].font = this;
+                  glyphs = shaping(fontEntries[fontEntryNum], utf16, wc, curScript, &numGlyphs, &rightToLeft);
+                  if(!numGlyphs)
+                     continue;
+
+                  glyphIndex = rightToLeft ? (numGlyphs - 1) : 0;
+                  glyphNo = glyphs[glyphIndex] | 0x80000000 | (glyphScript << 24);
+                  rightToLeft ? glyphIndex-- : glyphIndex++;
+               }
+            }
+
+            curFontEntry = fontEntries[fontEntryNum];
+
+            packNo = glyphNo & 0xFFFFFF80;
+
+            if(packNo != lastPack)
+            {
+               if(glyphNo < 128)
+                  pack = asciiPack;
+               else
+               {
+                  pack = (GlyphPack)glyphPacks.Find((uintptr)packNo);
+                  if(!pack)
+                  {
+                     pack = GlyphPack { key = (uintptr)packNo };
+                     glyphPacks.Add(pack);
+                     pack.Render(this, fontEntryNum, displaySystem);
+                  }
+               }
+               pack.bitmap.alphaBlend = true;
+               lastPack = packNo;
+            }
+            if(pack)
+            {
+               int index = rightToLeft ? (glyphIndex + 1) : (glyphIndex-1);
+               Glyph * glyph = &pack.glyphs[glyphNo & 0x7F];
+
+               int ax = (int)((numGlyphs ? shaper_item.advances[index] : glyph->ax) * glyph->scale);
+               int offset = numGlyphs ? shaper_item.offsets[index].x : 0;
+               int oy = 0;//numGlyphs ? shaper_item.offsets[index].y : 0;
+
+               ax += offset;
+
+               if(previousGlyph && curFontEntry && (curFontEntry.face == previousFace || !previousFace)) // TO IMPROVE: Assuming same face for now for multiple calls...
+               {
+                  FT_Vector delta = { 0, 0 };
+                  FT_Get_Kerning(curFontEntry.face, previousGlyph, glyph->glyphNo, FT_KERNING_UNFITTED, &delta );
+                  if(delta.x < 0)  delta.x += (-delta.x) % 64;
+                  else if(delta.x) delta.x += 64 - (delta.x % 64);
+                  *x += delta.x * glyph->scale;
+               }
+               else if(curFontEntry)
+                  FaceSetCharSize(curFontEntry.face, size);
+
+               previousGlyph = glyph->glyphNo;
+               previousFace = curFontEntry ? curFontEntry.face : null;
+
+               if(callback)
+                  callback(surface, display, ((*x) >> 6), y + (oy >> 6), glyph, pack.bitmap);
+               *x += ax;
+
+               lastGlyph = glyph;
+               lastAX = ax;
+            }
+            if(numGlyphs && (rightToLeft ? (glyphIndex < 0) : (glyphIndex == numGlyphs)))
+               numGlyphs = 0;
+         }
+         if(lastGlyph)
+         {
+            int w = (lastGlyph->w + lastGlyph->left) * (1 << 6);
+            // Fix for advance != width + left (e.g. italic fonts)
+            if(w > lastAX)
+               *advance = w - lastAX;
+         }
+         if(rPrevGlyph) *rPrevGlyph = previousGlyph;
+      }
+      if(surface)
+      {
+         LFBSurface lfbSurface = surface.driverData;
+         lfbSurface.xOffset = 0;
+      }
+#endif
+   }
+};