freetype-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[freetype2-demos] master 0bd9018: [ftinspect] Split sources; change face


From: Werner LEMBERG
Subject: [freetype2-demos] master 0bd9018: [ftinspect] Split sources; change face index type to `long'.
Date: Sat, 27 May 2017 05:59:45 -0400 (EDT)

branch: master
commit 0bd90186f4969b1090e0ab4a99eab5a95ea6449e
Author: Philipp Kerling <address@hidden>
Commit: Werner Lemberg <address@hidden>

    [ftinspect] Split sources; change face index type to `long'.
    
    * src/ftinspect.cpp, src/ftinspect.h: Split into...
    * src/ftinspect/*: ...these new files.
    Do face index type change.
    
    * src/ftinspect/.gitignore: New file.
---
 ChangeLog                                     |   10 +
 src/ftinspect.cpp                             | 2756 -------------------------
 src/ftinspect.pro                             |   28 -
 src/ftinspect/.gitignore                      |    6 +
 src/ftinspect/engine/engine.cpp               |  649 ++++++
 src/ftinspect/engine/engine.hpp               |  122 ++
 src/ftinspect/ftinspect-build.sh              |    9 +
 src/ftinspect/ftinspect-qt4                   |  Bin 0 -> 4855728 bytes
 src/ftinspect/ftinspect-qt4.pro               |   58 +
 src/ftinspect/ftinspect-qt5                   |  Bin 0 -> 5390560 bytes
 src/ftinspect/ftinspect-qt5.pro               |   58 +
 src/ftinspect/ftinspect.cpp                   |   36 +
 src/ftinspect/ftinspect.pro                   |   57 +
 src/ftinspect/maingui.cpp                     | 1277 ++++++++++++
 src/{ftinspect.h => ftinspect/maingui.hpp}    |  316 +--
 src/ftinspect/rendering/glyphbitmap.cpp       |  128 ++
 src/ftinspect/rendering/glyphbitmap.hpp       |   41 +
 src/ftinspect/rendering/glyphoutline.cpp      |  129 ++
 src/ftinspect/rendering/glyphoutline.hpp      |   34 +
 src/ftinspect/rendering/glyphpointnumbers.cpp |  206 ++
 src/ftinspect/rendering/glyphpointnumbers.hpp |   36 +
 src/ftinspect/rendering/glyphpoints.cpp       |  108 +
 src/ftinspect/rendering/glyphpoints.hpp       |   36 +
 src/ftinspect/rendering/grid.cpp              |   93 +
 src/ftinspect/rendering/grid.hpp              |   29 +
 src/ftinspect/widgets/qcomboboxx.cpp          |   37 +
 src/ftinspect/widgets/qcomboboxx.hpp          |   24 +
 src/ftinspect/widgets/qgraphicsviewx.cpp      |   49 +
 src/ftinspect/widgets/qgraphicsviewx.hpp      |   32 +
 src/ftinspect/widgets/qpushbuttonx.cpp        |   33 +
 src/ftinspect/widgets/qpushbuttonx.hpp        |   24 +
 src/ftinspect/widgets/qspinboxx.cpp           |   85 +
 src/ftinspect/widgets/qspinboxx.hpp           |   24 +
 33 files changed, 3449 insertions(+), 3081 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ff82e61..e4ec8a5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2017-05-27  Philipp Kerling  <address@hidden>
+
+       [ftinspect] Split sources; change face index type to `long'.
+
+       * src/ftinspect.cpp, src/ftinspect.h: Split into...
+       * src/ftinspect/*: ...these new files.
+       Do face index type change.
+
+       * src/ftinspect/.gitignore: New file.
+
 2017-05-24  Werner Lemberg  <address@hidden>
 
        Minor `fread' coding issues.
diff --git a/src/ftinspect.cpp b/src/ftinspect.cpp
deleted file mode 100644
index 1d8364e..0000000
--- a/src/ftinspect.cpp
+++ /dev/null
@@ -1,2756 +0,0 @@
-// ftinspect.cpp
-
-// Copyright (C) 2016 by Werner Lemberg.
-
-#include "ftinspect.h"
-
-#include <stdint.h>
-#include <cmath>
-#include <limits>
-#include <stdexcept>
-
-#define VERSION "X.Y.Z"
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// FaceID
-//
-/////////////////////////////////////////////////////////////////////////////
-
-FaceID::FaceID()
-: fontIndex(-1),
-  faceIndex(-1),
-  namedInstanceIndex(-1)
-{
-  // empty
-}
-
-
-FaceID::FaceID(int fontIdx,
-               int faceIdx,
-               int namedInstanceIdx)
-: fontIndex(fontIdx),
-  faceIndex(faceIdx),
-  namedInstanceIndex(namedInstanceIdx)
-{
-  // empty
-}
-
-
-bool
-FaceID::operator<(const FaceID& other) const
-{
-  bool ret = false;
-
-  if (fontIndex < other.fontIndex)
-    ret = true;
-  else if (fontIndex == other.fontIndex)
-  {
-    if (faceIndex < other.faceIndex)
-      ret = true;
-    else if (faceIndex == other.faceIndex)
-    {
-      if (namedInstanceIndex < other.namedInstanceIndex)
-        ret = true;
-    }
-  }
-
-  return ret;
-}
-
-
-// The face requester is a function provided by the client application to
-// the cache manager to translate an `abstract' face ID into a real
-// `FT_Face' object.
-//
-// We use a map: `faceID' is the value, and its associated key gives the
-// font, face, and named instance indices.  Getting a key from a value is
-// slow, but this must be done only once, since `faceRequester' is only
-// called if the font is not yet in the cache.
-
-FT_Error
-faceRequester(FTC_FaceID ftcFaceID,
-              FT_Library library,
-              FT_Pointer requestData,
-              FT_Face* faceP)
-{
-  MainGUI* gui = static_cast<MainGUI*>(requestData);
-
-  // `ftcFaceID' is actually an integer
-  // -> first convert pointer to same-width integer, then discard superfluous
-  //    bits (e.g., on x86_64 where pointers are wider than int)
-  int val = static_cast<int>(reinterpret_cast<intptr_t>(ftcFaceID));
-  // make sure this does not cause information loss
-  Q_ASSERT_X(sizeof(void*) >= sizeof(int),
-             "faceRequester",
-             "Pointer size must be at least the size of int"
-             " in order to treat FTC_FaceID correctly");
-
-  const FaceID& faceID = gui->engine->faceIDMap.key(val);
-
-  // this is the only place where we have to check the validity of the font
-  // index; note that the validity of both the face and named instance index
-  // is checked by FreeType itself
-  if (faceID.fontIndex < 0
-      || faceID.fontIndex >= gui->fontList.size())
-    return FT_Err_Invalid_Argument;
-
-  QString& font = gui->fontList[faceID.fontIndex];
-  int faceIndex = faceID.faceIndex;
-
-  if (faceID.namedInstanceIndex > 0)
-    faceIndex += faceID.namedInstanceIndex << 16;
-
-  return FT_New_Face(library,
-                     qPrintable(font),
-                     faceIndex,
-                     faceP);
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Engine
-//
-/////////////////////////////////////////////////////////////////////////////
-
-Engine::Engine(MainGUI* g)
-{
-  gui = g;
-  ftSize = NULL;
-  // we reserve value 0 for the `invalid face ID'
-  faceCounter = 1;
-
-  FT_Error error;
-
-  error = FT_Init_FreeType(&library);
-  if (error)
-  {
-    // XXX error handling
-  }
-
-  error = FTC_Manager_New(library, 0, 0, 0,
-                          faceRequester, gui, &cacheManager);
-  if (error)
-  {
-    // XXX error handling
-  }
-
-  error = FTC_SBitCache_New(cacheManager, &sbitsCache);
-  if (error)
-  {
-    // XXX error handling
-  }
-
-  error = FTC_ImageCache_New(cacheManager, &imageCache);
-  if (error)
-  {
-    // XXX error handling
-  }
-
-  // query engines and check for alternatives
-
-  // CFF
-  error = FT_Property_Get(library,
-                          "cff",
-                          "hinting-engine",
-                          &cffHintingEngineDefault);
-  if (error)
-  {
-    // no CFF engine
-    cffHintingEngineDefault = -1;
-    cffHintingEngineOther = -1;
-  }
-  else
-  {
-    int engines[] =
-    {
-      FT_CFF_HINTING_FREETYPE,
-      FT_CFF_HINTING_ADOBE
-    };
-
-    int i;
-    for (i = 0; i < 2; i++)
-      if (cffHintingEngineDefault == engines[i])
-        break;
-
-    cffHintingEngineOther = engines[(i + 1) % 2];
-
-    error = FT_Property_Set(library,
-                            "cff",
-                            "hinting-engine",
-                            &cffHintingEngineOther);
-    if (error)
-      cffHintingEngineOther = -1;
-
-    // reset
-    FT_Property_Set(library,
-                    "cff",
-                    "hinting-engine",
-                    &cffHintingEngineDefault);
-  }
-
-  // TrueType
-  error = FT_Property_Get(library,
-                          "truetype",
-                          "interpreter-version",
-                          &ttInterpreterVersionDefault);
-  if (error)
-  {
-    // no TrueType engine
-    ttInterpreterVersionDefault = -1;
-    ttInterpreterVersionOther = -1;
-    ttInterpreterVersionOther1 = -1;
-  }
-  else
-  {
-    int interpreters[] =
-    {
-      TT_INTERPRETER_VERSION_35,
-      TT_INTERPRETER_VERSION_38,
-      TT_INTERPRETER_VERSION_40
-    };
-
-    int i;
-    for (i = 0; i < 3; i++)
-      if (ttInterpreterVersionDefault == interpreters[i])
-        break;
-
-    ttInterpreterVersionOther = interpreters[(i + 1) % 3];
-
-    error = FT_Property_Set(library,
-                            "truetype",
-                            "interpreter-version",
-                            &ttInterpreterVersionOther);
-    if (error)
-      ttInterpreterVersionOther = -1;
-
-    ttInterpreterVersionOther1 = interpreters[(i + 2) % 3];
-
-    error = FT_Property_Set(library,
-                            "truetype",
-                            "interpreter-version",
-                            &ttInterpreterVersionOther1);
-    if (error)
-      ttInterpreterVersionOther1 = -1;
-
-    // reset
-    FT_Property_Set(library,
-                    "truetype",
-                    "interpreter-version",
-                    &ttInterpreterVersionDefault);
-  }
-
-  // auto-hinter
-  error = FT_Property_Get(library,
-                          "autofitter",
-                          "warping",
-                          &doWarping);
-  if (error)
-  {
-    // no warping
-    haveWarping = 0;
-    doWarping = 0;
-  }
-  else
-  {
-    haveWarping = 1;
-    doWarping = 0; // we don't do warping by default
-
-    FT_Property_Set(library,
-                    "autofitter",
-                    "warping",
-                    &doWarping);
-  }
-}
-
-
-Engine::~Engine()
-{
-  FTC_Manager_Done(cacheManager);
-  FT_Done_FreeType(library);
-}
-
-
-int
-Engine::numberOfFaces(int fontIndex)
-{
-  FT_Face face;
-  int numFaces = -1;
-
-  // search triplet (fontIndex, 0, 0)
-  FTC_FaceID ftcFaceID = reinterpret_cast<void*>
-                           (faceIDMap.value(FaceID(fontIndex,
-                                                   0,
-                                                   0)));
-  if (ftcFaceID)
-  {
-    // found
-    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
-      numFaces = face->num_faces;
-  }
-  else
-  {
-    // not found; try to load triplet (fontIndex, 0, 0)
-    ftcFaceID = reinterpret_cast<void*>(faceCounter);
-    faceIDMap.insert(FaceID(fontIndex, 0, 0),
-                     faceCounter++);
-
-    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
-      numFaces = face->num_faces;
-    else
-    {
-      faceIDMap.remove(FaceID(fontIndex, 0, 0));
-      faceCounter--;
-    }
-  }
-
-  return numFaces;
-}
-
-
-int
-Engine::numberOfNamedInstances(int fontIndex,
-                               int faceIndex)
-{
-  FT_Face face;
-  // we return `n' named instances plus one;
-  // instance index 0 represents a face without a named instance selected
-  int numNamedInstances = -1;
-
-  // search triplet (fontIndex, faceIndex, 0)
-  FTC_FaceID ftcFaceID = reinterpret_cast<void*>
-                           (faceIDMap.value(FaceID(fontIndex,
-                                                   faceIndex,
-                                                   0)));
-  if (ftcFaceID)
-  {
-    // found
-    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
-      numNamedInstances = static_cast<int>((face->style_flags >> 16) + 1);
-  }
-  else
-  {
-    // not found; try to load triplet (fontIndex, faceIndex, 0)
-    ftcFaceID = reinterpret_cast<void*>(faceCounter);
-    faceIDMap.insert(FaceID(fontIndex, faceIndex, 0),
-                     faceCounter++);
-
-    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
-      numNamedInstances = static_cast<int>((face->style_flags >> 16) + 1);
-    else
-    {
-      faceIDMap.remove(FaceID(fontIndex, faceIndex, 0));
-      faceCounter--;
-    }
-  }
-
-  return numNamedInstances;
-}
-
-
-int
-Engine::loadFont(int fontIndex,
-                 int faceIndex,
-                 int namedInstanceIndex)
-{
-  int numGlyphs = -1;
-  fontType = FontType_Other;
-
-  update();
-
-  // search triplet (fontIndex, faceIndex, namedInstanceIndex)
-  scaler.face_id = reinterpret_cast<void*>
-                     (faceIDMap.value(FaceID(fontIndex,
-                                             faceIndex,
-                                             namedInstanceIndex)));
-  if (scaler.face_id)
-  {
-    // found
-    if (!FTC_Manager_LookupSize(cacheManager, &scaler, &ftSize))
-      numGlyphs = ftSize->face->num_glyphs;
-  }
-  else
-  {
-    // not found; try to load triplet
-    // (fontIndex, faceIndex, namedInstanceIndex)
-    scaler.face_id = reinterpret_cast<void*>(faceCounter);
-    faceIDMap.insert(FaceID(fontIndex,
-                            faceIndex,
-                            namedInstanceIndex),
-                     faceCounter++);
-
-    if (!FTC_Manager_LookupSize(cacheManager, &scaler, &ftSize))
-      numGlyphs = ftSize->face->num_glyphs;
-    else
-    {
-      faceIDMap.remove(FaceID(fontIndex,
-                              faceIndex,
-                              namedInstanceIndex));
-      faceCounter--;
-    }
-  }
-
-  if (numGlyphs < 0)
-  {
-    ftSize = NULL;
-    curFamilyName = QString();
-    curStyleName = QString();
-  }
-  else
-  {
-    curFamilyName = QString(ftSize->face->family_name);
-    curStyleName = QString(ftSize->face->style_name);
-
-    FT_Module module = &ftSize->face->driver->root;
-    const char* moduleName = module->clazz->module_name;
-
-    // XXX cover all available modules
-    if (!strcmp(moduleName, "cff"))
-      fontType = FontType_CFF;
-    else if (!strcmp(moduleName, "truetype"))
-      fontType = FontType_TrueType;
-  }
-
-  return numGlyphs;
-}
-
-
-void
-Engine::removeFont(int fontIndex)
-{
-  // we iterate over all triplets that contain the given font index
-  // and remove them
-  QMap<FaceID, int>::iterator iter
-    = faceIDMap.lowerBound(FaceID(fontIndex, 0, 0));
-
-  for (;;)
-  {
-    if (iter == faceIDMap.end())
-      break;
-
-    FaceID faceID = iter.key();
-    if (faceID.fontIndex != fontIndex)
-      break;
-
-    FTC_FaceID ftcFaceID = reinterpret_cast<void*>(iter.value());
-    FTC_Manager_RemoveFaceID(cacheManager, ftcFaceID);
-
-    iter = faceIDMap.erase(iter);
-  }
-}
-
-
-const QString&
-Engine::currentFamilyName()
-{
-  return curFamilyName;
-}
-
-
-const QString&
-Engine::currentStyleName()
-{
-  return curStyleName;
-}
-
-
-QString
-Engine::glyphName(int index)
-{
-  QString name;
-
-  if (index < 0)
-    throw std::runtime_error("Invalid glyph index");
-
-  if (ftSize && FT_HAS_GLYPH_NAMES(ftSize->face))
-  {
-    char buffer[256];
-    if (!FT_Get_Glyph_Name(ftSize->face,
-                           static_cast<unsigned int>(index),
-                           buffer,
-                           sizeof(buffer)))
-      name = QString(buffer);
-  }
-
-  return name;
-}
-
-
-FT_Outline*
-Engine::loadOutline(int glyphIndex)
-{
-  update();
-
-  if (glyphIndex < 0)
-    throw std::runtime_error("Invalid glyph index");
-
-  FT_Glyph glyph;
-
-  // XXX handle bitmap fonts
-
-  // the `scaler' object is set up by the
-  // `update' and `loadFont' methods
-  if (FTC_ImageCache_LookupScaler(imageCache,
-                                  &scaler,
-                                  loadFlags | FT_LOAD_NO_BITMAP,
-                                  static_cast<unsigned int>(glyphIndex),
-                                  &glyph,
-                                  NULL))
-  {
-    // XXX error handling?
-    return NULL;
-  }
-
-  if (glyph->format != FT_GLYPH_FORMAT_OUTLINE)
-    return NULL;
-
-  FT_OutlineGlyph outlineGlyph = reinterpret_cast<FT_OutlineGlyph>(glyph);
-
-  return &outlineGlyph->outline;
-}
-
-
-void
-Engine::setCFFHintingMode(int mode)
-{
-  int index = gui->hintingModesCFFHash.key(mode);
-
-  FT_Error error = FT_Property_Set(library,
-                                   "cff",
-                                   "hinting-engine",
-                                   &index);
-  if (!error)
-  {
-    // reset the cache
-    FTC_Manager_Reset(cacheManager);
-  }
-}
-
-
-void
-Engine::setTTInterpreterVersion(int mode)
-{
-  int index = gui->hintingModesTrueTypeHash.key(mode);
-
-  FT_Error error = FT_Property_Set(library,
-                                   "truetype",
-                                   "interpreter-version",
-                                   &index);
-  if (!error)
-  {
-    // reset the cache
-    FTC_Manager_Reset(cacheManager);
-  }
-}
-
-
-void
-Engine::update()
-{
-  // Spinbox value cannot become negative
-  dpi = static_cast<unsigned int>(gui->dpiSpinBox->value());
-
-  if (gui->unitsComboBox->currentIndex() == MainGUI::Units_px)
-  {
-    pixelSize = gui->sizeDoubleSpinBox->value();
-    pointSize = pixelSize * 72.0 / dpi;
-  }
-  else
-  {
-    pointSize = gui->sizeDoubleSpinBox->value();
-    pixelSize = pointSize * dpi / 72.0;
-  }
-
-  doHinting = gui->hintingCheckBox->isChecked();
-
-  doAutoHinting = gui->autoHintingCheckBox->isChecked();
-  doHorizontalHinting = gui->horizontalHintingCheckBox->isChecked();
-  doVerticalHinting = gui->verticalHintingCheckBox->isChecked();
-  doBlueZoneHinting = gui->blueZoneHintingCheckBox->isChecked();
-  showSegments = gui->segmentDrawingCheckBox->isChecked();
-  doWarping = gui->warpingCheckBox->isChecked();
-
-  gamma = gui->gammaSlider->value();
-
-  loadFlags = FT_LOAD_DEFAULT;
-  if (doAutoHinting)
-    loadFlags |= FT_LOAD_FORCE_AUTOHINT;
-  loadFlags |= FT_LOAD_NO_BITMAP; // XXX handle bitmap fonts also
-
-  int index = gui->antiAliasingComboBoxx->currentIndex();
-
-  if (doHinting)
-  {
-    unsigned long target;
-
-    if (index == MainGUI::AntiAliasing_None)
-      target = FT_LOAD_TARGET_MONO;
-    else
-    {
-      switch (index)
-      {
-      case MainGUI::AntiAliasing_Light:
-        target = FT_LOAD_TARGET_LIGHT;
-        break;
-
-      case MainGUI::AntiAliasing_LCD:
-      case MainGUI::AntiAliasing_LCD_BGR:
-        target = FT_LOAD_TARGET_LCD;
-        break;
-
-      case MainGUI::AntiAliasing_LCD_Vertical:
-      case MainGUI::AntiAliasing_LCD_Vertical_BGR:
-        target = FT_LOAD_TARGET_LCD_V;
-        break;
-
-      default:
-        target = FT_LOAD_TARGET_NORMAL;
-      }
-    }
-
-    loadFlags |= target;
-  }
-  else
-  {
-    loadFlags |= FT_LOAD_NO_HINTING;
-
-    if (index == MainGUI::AntiAliasing_None)
-      loadFlags |= FT_LOAD_MONOCHROME;
-  }
-
-  // XXX handle color fonts also
-
-  scaler.pixel = 0; // use 26.6 format
-
-  if (gui->unitsComboBox->currentIndex() == MainGUI::Units_px)
-  {
-    scaler.width = static_cast<unsigned int>(pixelSize * 64.0);
-    scaler.height = static_cast<unsigned int>(pixelSize * 64.0);
-    scaler.x_res = 0;
-    scaler.y_res = 0;
-  }
-  else
-  {
-    scaler.width = static_cast<unsigned int>(pointSize * 64.0);
-    scaler.height = static_cast<unsigned int>(pointSize * 64.0);
-    scaler.x_res = dpi;
-    scaler.y_res = dpi;
-  }
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Grid
-//
-/////////////////////////////////////////////////////////////////////////////
-
-Grid::Grid(const QPen& gridP,
-           const QPen& axisP)
-: gridPen(gridP),
-  axisPen(axisP)
-{
- // empty
-}
-
-
-QRectF
-Grid::boundingRect() const
-{
-  // XXX fix size
-
-  // no need to take care of pen width
-  return QRectF(-100, -100,
-                200, 200);
-}
-
-
-// XXX call this in a `myQDraphicsView::drawBackground' derived method
-//     to always fill the complete viewport
-
-void
-Grid::paint(QPainter* painter,
-            const QStyleOptionGraphicsItem* option,
-            QWidget*)
-{
-  const qreal lod = option->levelOfDetailFromTransform(
-                              painter->worldTransform());
-
-  painter->setPen(gridPen);
-
-  // don't mark pixel center with a cross if magnification is too small
-  if (lod > 20)
-  {
-    int halfLength = 1;
-
-    // cf. QSpinBoxx
-    if (lod > 640)
-      halfLength = 6;
-    else if (lod > 320)
-      halfLength = 5;
-    else if (lod > 160)
-      halfLength = 4;
-    else if (lod > 80)
-      halfLength = 3;
-    else if (lod > 40)
-      halfLength = 2;
-
-    for (qreal x = -100; x < 100; x++)
-      for (qreal y = -100; y < 100; y++)
-      {
-        painter->drawLine(QLineF(x + 0.5, y + 0.5 - halfLength / lod,
-                                 x + 0.5, y + 0.5 + halfLength / lod));
-        painter->drawLine(QLineF(x + 0.5 - halfLength / lod, y + 0.5,
-                                 x + 0.5 + halfLength / lod, y + 0.5));
-      }
-  }
-
-  // don't draw grid if magnification is too small
-  if (lod >= 5)
-  {
-    // XXX fix size
-    for (int x = -100; x <= 100; x++)
-      painter->drawLine(x, -100,
-                        x, 100);
-    for (int y = -100; y <= 100; y++)
-      painter->drawLine(-100, y,
-                        100, y);
-  }
-
-  painter->setPen(axisPen);
-
-  painter->drawLine(0, -100,
-                    0, 100);
-  painter->drawLine(-100, 0,
-                    100, 0);
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// GlyphOutline
-//
-/////////////////////////////////////////////////////////////////////////////
-
-extern "C" {
-
-// vertical font coordinates are bottom-up,
-// while Qt uses top-down
-
-static int
-moveTo(const FT_Vector* to,
-       void* user)
-{
-  QPainterPath* path = static_cast<QPainterPath*>(user);
-
-  path->moveTo(qreal(to->x) / 64,
-               -qreal(to->y) / 64);
-
-  return 0;
-}
-
-
-static int
-lineTo(const FT_Vector* to,
-       void* user)
-{
-  QPainterPath* path = static_cast<QPainterPath*>(user);
-
-  path->lineTo(qreal(to->x) / 64,
-               -qreal(to->y) / 64);
-
-  return 0;
-}
-
-
-static int
-conicTo(const FT_Vector* control,
-        const FT_Vector* to,
-        void* user)
-{
-  QPainterPath* path = static_cast<QPainterPath*>(user);
-
-  path->quadTo(qreal(control->x) / 64,
-               -qreal(control->y) / 64,
-               qreal(to->x) / 64,
-               -qreal(to->y) / 64);
-
-  return 0;
-}
-
-
-static int
-cubicTo(const FT_Vector* control1,
-        const FT_Vector* control2,
-        const FT_Vector* to,
-        void* user)
-{
-  QPainterPath* path = static_cast<QPainterPath*>(user);
-
-  path->cubicTo(qreal(control1->x) / 64,
-                -qreal(control1->y) / 64,
-                qreal(control2->x) / 64,
-                -qreal(control2->y) / 64,
-                qreal(to->x) / 64,
-                -qreal(to->y) / 64);
-
-  return 0;
-}
-
-
-static FT_Outline_Funcs outlineFuncs =
-{
-  moveTo,
-  lineTo,
-  conicTo,
-  cubicTo,
-  0, // no shift
-  0  // no delta
-};
-
-} // extern "C"
-
-
-GlyphOutline::GlyphOutline(const QPen& outlineP,
-                           FT_Outline* outln)
-: outlinePen(outlineP),
-  outline(outln)
-{
-  FT_BBox cbox;
-
-  qreal halfPenWidth = outlinePen.widthF();
-
-  FT_Outline_Get_CBox(outline, &cbox);
-
-  bRect.setCoords(qreal(cbox.xMin) / 64 - halfPenWidth,
-                  -qreal(cbox.yMax) / 64 - halfPenWidth,
-                  qreal(cbox.xMax) / 64 + halfPenWidth,
-                  -qreal(cbox.yMin) / 64 + halfPenWidth);
-}
-
-
-QRectF
-GlyphOutline::boundingRect() const
-{
-  return bRect;
-}
-
-
-void
-GlyphOutline::paint(QPainter* painter,
-                    const QStyleOptionGraphicsItem*,
-                    QWidget*)
-{
-  painter->setPen(outlinePen);
-
-  QPainterPath path;
-  FT_Outline_Decompose(outline, &outlineFuncs, &path);
-
-  painter->drawPath(path);
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// GlyphPoints
-//
-/////////////////////////////////////////////////////////////////////////////
-
-GlyphPoints::GlyphPoints(const QPen& onP,
-                         const QPen& offP,
-                         FT_Outline* outln)
-: onPen(onP),
-  offPen(offP),
-  outline(outln)
-{
-  FT_BBox cbox;
-
-  qreal halfPenWidth = qMax(onPen.widthF(), offPen.widthF()) / 2;
-
-  FT_Outline_Get_CBox(outline, &cbox);
-
-  bRect.setCoords(qreal(cbox.xMin) / 64 - halfPenWidth,
-                  -qreal(cbox.yMax) / 64 - halfPenWidth,
-                  qreal(cbox.xMax) / 64 + halfPenWidth,
-                  -qreal(cbox.yMin) / 64 + halfPenWidth);
-}
-
-
-QRectF
-GlyphPoints::boundingRect() const
-{
-  return bRect;
-}
-
-
-void
-GlyphPoints::paint(QPainter* painter,
-                   const QStyleOptionGraphicsItem* option,
-                   QWidget*)
-{
-  const qreal lod = option->levelOfDetailFromTransform(
-                              painter->worldTransform());
-
-  // don't draw points if magnification is too small
-  if (lod >= 5)
-  {
-    // we want the same dot size regardless of the scaling;
-    // for good optical results, the pen widths should be uneven integers
-
-    // interestingly, using `drawPoint' doesn't work as expected:
-    // the larger the zoom, the more horizontally stretched the dot appears
-#if 0
-    qreal origOnPenWidth = onPen.widthF();
-    qreal origOffPenWidth = offPen.widthF();
-
-    onPen.setWidthF(origOnPenWidth / lod);
-    offPen.setWidthF(origOffPenWidth / lod);
-
-    for (int i = 0; i < outline->n_points; i++)
-    {
-      if (outline->tags[i] & FT_CURVE_TAG_ON)
-        painter->setPen(onPen);
-      else
-        painter->setPen(offPen);
-
-      painter->drawPoint(QPointF(qreal(outline->points[i].x) / 64,
-                                 -qreal(outline->points[i].y) / 64));
-    }
-
-    onPen.setWidthF(origOnPenWidth);
-    offPen.setWidthF(origOffPenWidth);
-#else
-    QBrush onBrush(onPen.color());
-    QBrush offBrush(offPen.color());
-
-    painter->setPen(Qt::NoPen);
-
-    qreal onRadius = onPen.widthF() / lod;
-    qreal offRadius = offPen.widthF() / lod;
-
-    for (int i = 0; i < outline->n_points; i++)
-    {
-      if (outline->tags[i] & FT_CURVE_TAG_ON)
-      {
-        painter->setBrush(onBrush);
-        painter->drawEllipse(QPointF(qreal(outline->points[i].x) / 64,
-                                     -qreal(outline->points[i].y) / 64),
-                             onRadius,
-                             onRadius);
-      }
-      else
-      {
-        painter->setBrush(offBrush);
-        painter->drawEllipse(QPointF(qreal(outline->points[i].x) / 64,
-                                     -qreal(outline->points[i].y) / 64),
-                             offRadius,
-                             offRadius);
-      }
-    }
-#endif
-  }
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// GlyphPointNumbers
-//
-/////////////////////////////////////////////////////////////////////////////
-
-GlyphPointNumbers::GlyphPointNumbers(const QPen& onP,
-                                     const QPen& offP,
-                                     FT_Outline* outln)
-: onPen(onP),
-  offPen(offP),
-  outline(outln)
-{
-  FT_BBox cbox;
-
-  FT_Outline_Get_CBox(outline, &cbox);
-
-  // XXX fix bRect size
-  bRect.setCoords(qreal(cbox.xMin) / 64,
-                  -qreal(cbox.yMax) / 64,
-                  qreal(cbox.xMax) / 64,
-                  -qreal(cbox.yMin) / 64);
-}
-
-
-QRectF
-GlyphPointNumbers::boundingRect() const
-{
-  return bRect;
-}
-
-
-void
-GlyphPointNumbers::paint(QPainter* painter,
-                         const QStyleOptionGraphicsItem* option,
-                         QWidget*)
-{
-  const qreal lod = option->levelOfDetailFromTransform(
-                              painter->worldTransform());
-
-  // don't draw point numbers if magnification is too small
-  if (lod >= 10)
-  {
-    QFont font = painter->font();
-
-    // the following doesn't work correctly with scaling;
-    // it seems that Qt doesn't allow arbitrarily small font sizes
-    // that get magnified later on
-#if 0
-    // we want the same text size regardless of the scaling
-    font.setPointSizeF(font.pointSizeF() / lod);
-    painter->setFont(font);
-#else
-    font.setPointSizeF(font.pointSizeF() * 3 / 4);
-    painter->setFont(font);
-
-    QBrush onBrush(onPen.color());
-    QBrush offBrush(offPen.color());
-
-    painter->scale(1 / lod, 1 / lod);
-#endif
-
-    FT_Vector* points = outline->points;
-    FT_Short* contours = outline->contours;
-    char* tags = outline->tags;
-
-    QVector2D octants[8] = { QVector2D(1, 0),
-                             QVector2D(0.707f, -0.707f),
-                             QVector2D(0, -1),
-                             QVector2D(-0.707f, -0.707f),
-                             QVector2D(-1, 0),
-                             QVector2D(-0.707f, 0.707f),
-                             QVector2D(0, 1),
-                             QVector2D(0.707f, 0.707f) };
-
-
-    short ptIdx = 0;
-    for (int contIdx = 0; contIdx < outline->n_contours; contIdx++ )
-    {
-      for (;;)
-      {
-        short prevIdx, nextIdx;
-
-        // find previous and next point in outline
-        if (contIdx == 0)
-        {
-          if (contours[contIdx] == 0)
-          {
-            prevIdx = 0;
-            nextIdx = 0;
-          }
-          else
-          {
-            prevIdx = ptIdx > 0 ? ptIdx - 1
-                                : contours[contIdx];
-            nextIdx = ptIdx < contours[contIdx] ? ptIdx + 1
-                                                : 0;
-          }
-        }
-        else
-        {
-          prevIdx = ptIdx > (contours[contIdx - 1] + 1) ? ptIdx - 1
-                                                        : contours[contIdx];
-          nextIdx = ptIdx < contours[contIdx] ? ptIdx + 1
-                                              : contours[contIdx - 1] + 1;
-        }
-
-        // get vectors to previous and next point and normalize them;
-        QVector2D in(static_cast<float>(points[prevIdx].x
-                                        - points[ptIdx].x) / 64,
-                     -static_cast<float>(points[prevIdx].y
-                                         - points[ptIdx].y) / 64);
-        QVector2D out(static_cast<float>(points[nextIdx].x
-                                         - points[ptIdx].x) / 64,
-                      -static_cast<float>(points[nextIdx].y
-                                          - points[ptIdx].y) / 64);
-
-        in = in.normalized();
-        out = out.normalized();
-
-        QVector2D middle = in + out;
-        // check whether vector is very small, using a threshold of 1/8px
-        if (qAbs(middle.x()) < 1.0f / 8
-            && qAbs(middle.y()) < 1.0f / 8)
-        {
-          // in case of vectors in almost exactly opposite directions,
-          // use a vector orthogonal to them
-          middle.setX(out.y());
-          middle.setY(-out.x());
-
-          if (qAbs(middle.x()) < 1.0f / 8
-              && qAbs(middle.y()) < 1.0f / 8)
-          {
-            // use direction based on point index for the offset
-            // if we still don't have a good value
-            middle = octants[ptIdx % 8];
-          }
-        }
-
-        // normalize `middle' vector (which is never zero),
-        // then multiply by 8 to get some distance between
-        // the point and the number
-        middle = middle.normalized() * 8;
-
-        // we now position the point number in the opposite
-        // direction of the `middle' vector,
-        QString number = QString::number(ptIdx);
-
-#if 0
-        // this fails, see comment above
-        int size = 10000;
-        qreal x = qreal(points[ptIdx].x) / 64 - middle.x() / lod;
-        qreal y = -qreal(points[ptIdx].y) / 64 - middle.y() / lod;
-        QPointF corner(x, y);
-        int flags = middle.x() > 0 ? Qt::AlignRight
-                                   : Qt::AlignLeft;
-        if (flags == Qt::AlignRight)
-          corner.rx() -= size;
-        QRectF posRect(corner, QSizeF(size, size));
-
-        if (tags[ptIdx] & FT_CURVE_TAG_ON)
-          painter->setPen(onPen);
-        else
-          painter->setPen(offPen);
-
-        painter->drawText(posRect, flags, number);
-#else
-        // convert text string to a path object
-        QPainterPath path;
-        path.addText(QPointF(0, 0), font, number);
-        QRectF ctrlPtRect = path.controlPointRect();
-
-        qreal x = static_cast<qreal>(points[ptIdx].x) / 64 * lod
-                  - static_cast<qreal>(middle.x());
-        qreal y = -static_cast<qreal>(points[ptIdx].y) / 64 * lod
-                  - static_cast<qreal>(middle.y());
-
-        qreal heuristicOffset = 2;
-        if (middle.x() > 0)
-          path.translate(x - ctrlPtRect.width() - heuristicOffset,
-                         y + ctrlPtRect.height() / 2);
-        else
-          path.translate(x,
-                         y + ctrlPtRect.height() / 2);
-
-        painter->fillPath(path,
-                          tags[ptIdx] & FT_CURVE_TAG_ON ? onBrush
-                                                        : offBrush);
-#endif
-
-        ptIdx++;
-        if (ptIdx > contours[contIdx])
-          break;
-      }
-    }
-  }
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// GlyphBitmap
-//
-/////////////////////////////////////////////////////////////////////////////
-
-GlyphBitmap::GlyphBitmap(FT_Outline* outline,
-                         FT_Library lib,
-                         FT_Pixel_Mode pxlMode,
-                         const QVector<QRgb>& monoColorTbl,
-                         const QVector<QRgb>& grayColorTbl)
-: library(lib),
-  pixelMode(pxlMode),
-  monoColorTable(monoColorTbl),
-  grayColorTable(grayColorTbl)
-{
-  // make a copy of the outline since we are going to manipulate it
-  FT_Outline_New(library,
-                 static_cast<unsigned int>(outline->n_points),
-                 outline->n_contours,
-                 &transformed);
-  FT_Outline_Copy(outline, &transformed);
-
-  FT_BBox cbox;
-  FT_Outline_Get_CBox(outline, &cbox);
-
-  cbox.xMin &= ~63;
-  cbox.yMin &= ~63;
-  cbox.xMax = (cbox.xMax + 63) & ~63;
-  cbox.yMax = (cbox.yMax + 63) & ~63;
-
-  // we shift the outline to the origin for rendering later on
-  FT_Outline_Translate(&transformed, -cbox.xMin, -cbox.yMin);
-
-  bRect.setCoords(cbox.xMin / 64, -cbox.yMax / 64,
-                  cbox.xMax / 64, -cbox.yMin / 64);
-}
-
-
-GlyphBitmap::~GlyphBitmap()
-{
-  FT_Outline_Done(library, &transformed);
-}
-
-
-QRectF
-GlyphBitmap::boundingRect() const
-{
-  return bRect;
-}
-
-
-void
-GlyphBitmap::paint(QPainter* painter,
-                   const QStyleOptionGraphicsItem* option,
-                   QWidget*)
-{
-  FT_Bitmap bitmap;
-
-  int height = static_cast<int>(ceil(bRect.height()));
-  int width = static_cast<int>(ceil(bRect.width()));
-  QImage::Format format = QImage::Format_Indexed8;
-
-  // XXX cover LCD and color
-  if (pixelMode == FT_PIXEL_MODE_MONO)
-    format = QImage::Format_Mono;
-
-  QImage image(QSize(width, height), format);
-
-  if (pixelMode == FT_PIXEL_MODE_MONO)
-    image.setColorTable(monoColorTable);
-  else
-    image.setColorTable(grayColorTable);
-
-  image.fill(0);
-
-  bitmap.rows = static_cast<unsigned int>(height);
-  bitmap.width = static_cast<unsigned int>(width);
-  bitmap.buffer = image.bits();
-  bitmap.pitch = image.bytesPerLine();
-  bitmap.pixel_mode = pixelMode;
-
-  FT_Error error = FT_Outline_Get_Bitmap(library,
-                                         &transformed,
-                                         &bitmap);
-  if (error)
-  {
-    // XXX error handling
-    return;
-  }
-
-  // `drawImage' doesn't work as expected:
-  // the larger the zoom, the more the pixel rectangle positions
-  // deviate from the grid lines
-#if 0
-  painter->drawImage(QPoint(bRect.left(), bRect.top()),
-                     image.convertToFormat(
-                       QImage::Format_ARGB32_Premultiplied));
-#else
-  const qreal lod = option->levelOfDetailFromTransform(
-                              painter->worldTransform());
-
-  painter->setPen(Qt::NoPen);
-
-  for (int x = 0; x < image.width(); x++)
-    for (int y = 0; y < image.height(); y++)
-    {
-      // be careful not to lose the alpha channel
-      QRgb p = image.pixel(x, y);
-      painter->fillRect(QRectF(x + bRect.left() - 1 / lod / 2,
-                               y + bRect.top() - 1 / lod / 2,
-                               1 + 1 / lod,
-                               1 + 1 / lod),
-                        QColor(qRed(p),
-                               qGreen(p),
-                               qBlue(p),
-                               qAlpha(p)));
-    }
-#endif
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// MainGUI
-//
-/////////////////////////////////////////////////////////////////////////////
-
-MainGUI::MainGUI()
-{
-  engine = NULL;
-
-  fontWatcher = new QFileSystemWatcher;
-  // if the current input file is invalid we retry once a second to load it
-  timer = new QTimer;
-  timer->setInterval(1000);
-
-  setGraphicsDefaults();
-  createLayout();
-  createConnections();
-  createActions();
-  createMenus();
-  createStatusBar();
-
-  readSettings();
-
-  setUnifiedTitleAndToolBarOnMac(true);
-}
-
-
-MainGUI::~MainGUI()
-{
-  // empty
-}
-
-
-void
-MainGUI::update(Engine* e)
-{
-  engine = e;
-}
-
-
-// overloading
-
-void
-MainGUI::closeEvent(QCloseEvent* event)
-{
-  writeSettings();
-  event->accept();
-}
-
-
-void
-MainGUI::about()
-{
-  QMessageBox::about(
-    this,
-    tr("About ftinspect"),
-    tr("<p>This is <b>ftinspect</b> version %1<br>"
-       " Copyright %2 2016<br>"
-       " by Werner Lemberg <tt>&lt;address@hidden&gt;</tt></p>"
-       ""
-       "<p><b>ftinspect</b> shows how a font gets rendered"
-       " by FreeType, allowing control over virtually"
-       " all rendering parameters.</p>"
-       ""
-       "<p>License:"
-       " <a 
href='http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT'>FreeType"
-       " License (FTL)</a> or"
-       " <a 
href='http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT'>GNU"
-       " GPLv2</a></p>")
-       .arg(VERSION)
-       .arg(QChar(0xA9)));
-}
-
-
-void
-MainGUI::aboutQt()
-{
-  QApplication::aboutQt();
-}
-
-
-void
-MainGUI::loadFonts()
-{
-  int oldSize = fontList.size();
-
-  QStringList files = QFileDialog::getOpenFileNames(
-                        this,
-                        tr("Load one or more fonts"),
-                        QDir::homePath(),
-                        "",
-                        NULL,
-                        QFileDialog::ReadOnly);
-
-  // XXX sort data, uniquify elements
-  fontList.append(files);
-
-  // if we have new fonts, set the current index to the first new one
-  if (oldSize < fontList.size())
-    currentFontIndex = oldSize;
-
-  showFont();
-}
-
-
-void
-MainGUI::closeFont()
-{
-  if (currentFontIndex < fontList.size())
-  {
-    engine->removeFont(currentFontIndex);
-    fontWatcher->removePath(fontList[currentFontIndex]);
-    fontList.removeAt(currentFontIndex);
-  }
-
-  // show next font after deletion, i.e., retain index if possible
-  if (fontList.size())
-  {
-    if (currentFontIndex >= fontList.size())
-      currentFontIndex = fontList.size() - 1;
-  }
-  else
-    currentFontIndex = 0;
-
-  showFont();
-}
-
-
-void
-MainGUI::watchCurrentFont()
-{
-  timer->stop();
-  showFont();
-}
-
-
-void
-MainGUI::showFont()
-{
-  // we do lazy computation of FT_Face objects
-
-  if (currentFontIndex < fontList.size())
-  {
-    QString& font = fontList[currentFontIndex];
-    QFileInfo fileInfo(font);
-    QString fontName = fileInfo.fileName();
-
-    if (fileInfo.exists())
-    {
-      // Qt's file watcher doesn't handle symlinks;
-      // we thus fall back to polling
-      if (fileInfo.isSymLink())
-      {
-        fontName.prepend("<i>");
-        fontName.append("</i>");
-        timer->start();
-      }
-      else
-        fontWatcher->addPath(font);
-    }
-    else
-    {
-      // On Unix-like systems, the symlink's target gets opened; this
-      // implies that deletion of a symlink doesn't make `engine->loadFont'
-      // fail since it operates on a file handle pointing to the target.
-      // For this reason, we remove the font to enforce a reload.
-      engine->removeFont(currentFontIndex);
-    }
-
-    fontFilenameLabel->setText(fontName);
-  }
-  else
-    fontFilenameLabel->clear();
-
-  currentNumberOfFaces
-    = engine->numberOfFaces(currentFontIndex);
-  currentNumberOfNamedInstances
-    = engine->numberOfNamedInstances(currentFontIndex,
-                                     currentFaceIndex);
-  currentNumberOfGlyphs
-    = engine->loadFont(currentFontIndex,
-                       currentFaceIndex,
-                       currentNamedInstanceIndex);
-
-  if (currentNumberOfGlyphs < 0)
-  {
-    // there might be various reasons why the current
-    // (file, face, instance) triplet is invalid or missing;
-    // we thus start our timer to periodically test
-    // whether the font starts working
-    if (currentFontIndex < fontList.size())
-      timer->start();
-  }
-
-  fontNameLabel->setText(QString("%1 %2")
-                         .arg(engine->currentFamilyName())
-                         .arg(engine->currentStyleName()));
-
-  checkCurrentFontIndex();
-  checkCurrentFaceIndex();
-  checkCurrentNamedInstanceIndex();
-  checkHinting();
-  adjustGlyphIndex(0);
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkHinting()
-{
-  if (hintingCheckBox->isChecked())
-  {
-    if (engine->fontType == Engine::FontType_CFF)
-    {
-      for (int i = 0; i < hintingModeComboBoxx->count(); i++)
-      {
-        if (hintingModesCFFHash.key(i, -1) != -1)
-          hintingModeComboBoxx->setItemEnabled(i, true);
-        else
-          hintingModeComboBoxx->setItemEnabled(i, false);
-      }
-
-      hintingModeComboBoxx->setCurrentIndex(currentCFFHintingMode);
-    }
-    else if (engine->fontType == Engine::FontType_TrueType)
-    {
-      for (int i = 0; i < hintingModeComboBoxx->count(); i++)
-      {
-        if (hintingModesTrueTypeHash.key(i, -1) != -1)
-          hintingModeComboBoxx->setItemEnabled(i, true);
-        else
-          hintingModeComboBoxx->setItemEnabled(i, false);
-      }
-
-      hintingModeComboBoxx->setCurrentIndex(currentTTInterpreterVersion);
-    }
-    else
-    {
-      hintingModeLabel->setEnabled(false);
-      hintingModeComboBoxx->setEnabled(false);
-    }
-
-    for (int i = 0; i < hintingModesAlwaysDisabled.size(); i++)
-      hintingModeComboBoxx->setItemEnabled(hintingModesAlwaysDisabled[i],
-                                           false);
-
-    autoHintingCheckBox->setEnabled(true);
-    checkAutoHinting();
-  }
-  else
-  {
-    hintingModeLabel->setEnabled(false);
-    hintingModeComboBoxx->setEnabled(false);
-
-    autoHintingCheckBox->setEnabled(false);
-    horizontalHintingCheckBox->setEnabled(false);
-    verticalHintingCheckBox->setEnabled(false);
-    blueZoneHintingCheckBox->setEnabled(false);
-    segmentDrawingCheckBox->setEnabled(false);
-    warpingCheckBox->setEnabled(false);
-
-    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, false);
-  }
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkHintingMode()
-{
-  int index = hintingModeComboBoxx->currentIndex();
-
-  if (engine->fontType == Engine::FontType_CFF)
-  {
-    engine->setCFFHintingMode(index);
-    currentCFFHintingMode = index;
-  }
-  else if (engine->fontType == Engine::FontType_TrueType)
-  {
-    engine->setTTInterpreterVersion(index);
-    currentTTInterpreterVersion = index;
-  }
-
-  // this enforces reloading of the font
-  showFont();
-}
-
-
-void
-MainGUI::checkAutoHinting()
-{
-  if (autoHintingCheckBox->isChecked())
-  {
-    hintingModeLabel->setEnabled(false);
-    hintingModeComboBoxx->setEnabled(false);
-
-    horizontalHintingCheckBox->setEnabled(true);
-    verticalHintingCheckBox->setEnabled(true);
-    blueZoneHintingCheckBox->setEnabled(true);
-    segmentDrawingCheckBox->setEnabled(true);
-    if (engine->haveWarping)
-      warpingCheckBox->setEnabled(true);
-
-    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, true);
-  }
-  else
-  {
-    if (engine->fontType == Engine::FontType_CFF
-        || engine->fontType == Engine::FontType_TrueType)
-    {
-      hintingModeLabel->setEnabled(true);
-      hintingModeComboBoxx->setEnabled(true);
-    }
-
-    horizontalHintingCheckBox->setEnabled(false);
-    verticalHintingCheckBox->setEnabled(false);
-    blueZoneHintingCheckBox->setEnabled(false);
-    segmentDrawingCheckBox->setEnabled(false);
-    warpingCheckBox->setEnabled(false);
-
-    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, false);
-
-    if (antiAliasingComboBoxx->currentIndex() == AntiAliasing_Light)
-      antiAliasingComboBoxx->setCurrentIndex(AntiAliasing_Normal);
-  }
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkAntiAliasing()
-{
-  int index = antiAliasingComboBoxx->currentIndex();
-
-  if (index == AntiAliasing_None
-      || index == AntiAliasing_Normal
-      || index == AntiAliasing_Light)
-  {
-    lcdFilterLabel->setEnabled(false);
-    lcdFilterComboBox->setEnabled(false);
-  }
-  else
-  {
-    lcdFilterLabel->setEnabled(true);
-    lcdFilterComboBox->setEnabled(true);
-  }
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkLcdFilter()
-{
-  int index = lcdFilterComboBox->currentIndex();
-  FT_Library_SetLcdFilter(engine->library, lcdFilterHash.key(index));
-}
-
-
-void
-MainGUI::checkShowPoints()
-{
-  if (showPointsCheckBox->isChecked())
-    showPointNumbersCheckBox->setEnabled(true);
-  else
-    showPointNumbersCheckBox->setEnabled(false);
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkUnits()
-{
-  int index = unitsComboBox->currentIndex();
-
-  if (index == Units_px)
-  {
-    dpiLabel->setEnabled(false);
-    dpiSpinBox->setEnabled(false);
-    sizeDoubleSpinBox->setSingleStep(1);
-    sizeDoubleSpinBox->setValue(qRound(sizeDoubleSpinBox->value()));
-  }
-  else
-  {
-    dpiLabel->setEnabled(true);
-    dpiSpinBox->setEnabled(true);
-    sizeDoubleSpinBox->setSingleStep(0.5);
-  }
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::adjustGlyphIndex(int delta)
-{
-  // only adjust current glyph index if we have a valid font
-  if (currentNumberOfGlyphs > 0)
-  {
-    currentGlyphIndex += delta;
-    currentGlyphIndex = qBound(0,
-                               currentGlyphIndex,
-                               currentNumberOfGlyphs - 1);
-  }
-
-  QString upperHex = QString::number(currentGlyphIndex, 16).toUpper();
-  glyphIndexLabel->setText(QString("%1 (0x%2)")
-                                   .arg(currentGlyphIndex)
-                                   .arg(upperHex));
-  glyphNameLabel->setText(engine->glyphName(currentGlyphIndex));
-
-  drawGlyph();
-}
-
-
-void
-MainGUI::checkCurrentFontIndex()
-{
-  if (fontList.size() < 2)
-  {
-    previousFontButton->setEnabled(false);
-    nextFontButton->setEnabled(false);
-  }
-  else if (currentFontIndex == 0)
-  {
-    previousFontButton->setEnabled(false);
-    nextFontButton->setEnabled(true);
-  }
-  else if (currentFontIndex >= fontList.size() - 1)
-  {
-    previousFontButton->setEnabled(true);
-    nextFontButton->setEnabled(false);
-  }
-  else
-  {
-    previousFontButton->setEnabled(true);
-    nextFontButton->setEnabled(true);
-  }
-}
-
-
-void
-MainGUI::checkCurrentFaceIndex()
-{
-  if (currentNumberOfFaces < 2)
-  {
-    previousFaceButton->setEnabled(false);
-    nextFaceButton->setEnabled(false);
-  }
-  else if (currentFaceIndex == 0)
-  {
-    previousFaceButton->setEnabled(false);
-    nextFaceButton->setEnabled(true);
-  }
-  else if (currentFaceIndex >= currentNumberOfFaces - 1)
-  {
-    previousFaceButton->setEnabled(true);
-    nextFaceButton->setEnabled(false);
-  }
-  else
-  {
-    previousFaceButton->setEnabled(true);
-    nextFaceButton->setEnabled(true);
-  }
-}
-
-
-void
-MainGUI::checkCurrentNamedInstanceIndex()
-{
-  if (currentNumberOfNamedInstances < 2)
-  {
-    previousNamedInstanceButton->setEnabled(false);
-    nextNamedInstanceButton->setEnabled(false);
-  }
-  else if (currentNamedInstanceIndex == 0)
-  {
-    previousNamedInstanceButton->setEnabled(false);
-    nextNamedInstanceButton->setEnabled(true);
-  }
-  else if (currentNamedInstanceIndex >= currentNumberOfNamedInstances - 1)
-  {
-    previousNamedInstanceButton->setEnabled(true);
-    nextNamedInstanceButton->setEnabled(false);
-  }
-  else
-  {
-    previousNamedInstanceButton->setEnabled(true);
-    nextNamedInstanceButton->setEnabled(true);
-  }
-}
-
-
-void
-MainGUI::previousFont()
-{
-  if (currentFontIndex > 0)
-  {
-    currentFontIndex--;
-    currentFaceIndex = 0;
-    currentNamedInstanceIndex = 0;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::nextFont()
-{
-  if (currentFontIndex < fontList.size() - 1)
-  {
-    currentFontIndex++;
-    currentFaceIndex = 0;
-    currentNamedInstanceIndex = 0;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::previousFace()
-{
-  if (currentFaceIndex > 0)
-  {
-    currentFaceIndex--;
-    currentNamedInstanceIndex = 0;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::nextFace()
-{
-  if (currentFaceIndex < currentNumberOfFaces - 1)
-  {
-    currentFaceIndex++;
-    currentNamedInstanceIndex = 0;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::previousNamedInstance()
-{
-  if (currentNamedInstanceIndex > 0)
-  {
-    currentNamedInstanceIndex--;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::nextNamedInstance()
-{
-  if (currentNamedInstanceIndex < currentNumberOfNamedInstances - 1)
-  {
-    currentNamedInstanceIndex++;
-    showFont();
-  }
-}
-
-
-void
-MainGUI::zoom()
-{
-  int scale = zoomSpinBox->value();
-
-  QTransform transform;
-  transform.scale(scale, scale);
-
-  // we want horizontal and vertical 1px lines displayed with full pixels;
-  // we thus have to shift the coordinate system accordingly, using a value
-  // that represents 0.5px (i.e., half the 1px line width) after the scaling
-  qreal shift = 0.5 / scale;
-  transform.translate(shift, shift);
-
-  glyphView->setTransform(transform);
-}
-
-
-void
-MainGUI::setGraphicsDefaults()
-{
-  // color tables (with suitable opacity values) for converting
-  // FreeType's pixmaps to something Qt understands
-  monoColorTable.append(QColor(Qt::transparent).rgba());
-  monoColorTable.append(QColor(Qt::black).rgba());
-
-  for (int i = 0xFF; i >= 0; i--)
-    grayColorTable.append(qRgba(i, i, i, 0xFF - i));
-
-  // XXX make this user-configurable
-
-  axisPen.setColor(Qt::black);
-  axisPen.setWidth(0);
-  blueZonePen.setColor(QColor(64, 64, 255, 64)); // light blue
-  blueZonePen.setWidth(0);
-  gridPen.setColor(Qt::lightGray);
-  gridPen.setWidth(0);
-  offPen.setColor(Qt::darkGreen);
-  offPen.setWidth(3);
-  onPen.setColor(Qt::red);
-  onPen.setWidth(3);
-  outlinePen.setColor(Qt::red);
-  outlinePen.setWidth(0);
-  segmentPen.setColor(QColor(64, 255, 128, 64)); // light green
-  segmentPen.setWidth(0);
-}
-
-
-void
-MainGUI::drawGlyph()
-{
-  // the call to `engine->loadOutline' updates FreeType's load flags
-
-  if (!engine)
-    return;
-
-  if (currentGlyphBitmapItem)
-  {
-    glyphScene->removeItem(currentGlyphBitmapItem);
-    delete currentGlyphBitmapItem;
-
-    currentGlyphBitmapItem = NULL;
-  }
-
-  if (currentGlyphOutlineItem)
-  {
-    glyphScene->removeItem(currentGlyphOutlineItem);
-    delete currentGlyphOutlineItem;
-
-    currentGlyphOutlineItem = NULL;
-  }
-
-  if (currentGlyphPointsItem)
-  {
-    glyphScene->removeItem(currentGlyphPointsItem);
-    delete currentGlyphPointsItem;
-
-    currentGlyphPointsItem = NULL;
-  }
-
-  if (currentGlyphPointNumbersItem)
-  {
-    glyphScene->removeItem(currentGlyphPointNumbersItem);
-    delete currentGlyphPointNumbersItem;
-
-    currentGlyphPointNumbersItem = NULL;
-  }
-
-  FT_Outline* outline = engine->loadOutline(currentGlyphIndex);
-  if (outline)
-  {
-    if (showBitmapCheckBox->isChecked())
-    {
-      // XXX support LCD
-      FT_Pixel_Mode pixelMode = FT_PIXEL_MODE_GRAY;
-      if (antiAliasingComboBoxx->currentIndex() == AntiAliasing_None)
-        pixelMode = FT_PIXEL_MODE_MONO;
-
-      currentGlyphBitmapItem = new GlyphBitmap(outline,
-                                               engine->library,
-                                               pixelMode,
-                                               monoColorTable,
-                                               grayColorTable);
-      glyphScene->addItem(currentGlyphBitmapItem);
-    }
-
-    if (showOutlinesCheckBox->isChecked())
-    {
-      currentGlyphOutlineItem = new GlyphOutline(outlinePen, outline);
-      glyphScene->addItem(currentGlyphOutlineItem);
-    }
-
-    if (showPointsCheckBox->isChecked())
-    {
-      currentGlyphPointsItem = new GlyphPoints(onPen, offPen, outline);
-      glyphScene->addItem(currentGlyphPointsItem);
-
-      if (showPointNumbersCheckBox->isChecked())
-      {
-        currentGlyphPointNumbersItem = new GlyphPointNumbers(onPen,
-                                                             offPen,
-                                                             outline);
-        glyphScene->addItem(currentGlyphPointNumbersItem);
-      }
-    }
-  }
-
-  glyphScene->update();
-}
-
-
-// XXX distances are specified in pixels,
-//     making the layout dependent on the output device resolution
-void
-MainGUI::createLayout()
-{
-  // left side
-  fontFilenameLabel = new QLabel;
-
-  hintingCheckBox = new QCheckBox(tr("Hinting"));
-
-  hintingModeLabel = new QLabel(tr("Hinting Mode"));
-  hintingModeLabel->setAlignment(Qt::AlignRight);
-  hintingModeComboBoxx = new QComboBoxx;
-  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v35,
-                                   tr("TrueType v35"));
-  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v38,
-                                   tr("TrueType v38"));
-  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v40,
-                                   tr("TrueType v40"));
-  hintingModeComboBoxx->insertItem(HintingMode_CFF_FreeType,
-                                   tr("CFF (FreeType)"));
-  hintingModeComboBoxx->insertItem(HintingMode_CFF_Adobe,
-                                   tr("CFF (Adobe)"));
-  hintingModeLabel->setBuddy(hintingModeComboBoxx);
-
-  autoHintingCheckBox = new QCheckBox(tr("Auto-Hinting"));
-  horizontalHintingCheckBox = new QCheckBox(tr("Horizontal Hinting"));
-  verticalHintingCheckBox = new QCheckBox(tr("Vertical Hinting"));
-  blueZoneHintingCheckBox = new QCheckBox(tr("Blue-Zone Hinting"));
-  segmentDrawingCheckBox = new QCheckBox(tr("Segment Drawing"));
-  warpingCheckBox = new QCheckBox(tr("Warping"));
-
-  antiAliasingLabel = new QLabel(tr("Anti-Aliasing"));
-  antiAliasingLabel->setAlignment(Qt::AlignRight);
-  antiAliasingComboBoxx = new QComboBoxx;
-  antiAliasingComboBoxx->insertItem(AntiAliasing_None,
-                                    tr("None"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_Normal,
-                                    tr("Normal"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_Light,
-                                    tr("Light"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD,
-                                    tr("LCD (RGB)"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_BGR,
-                                    tr("LCD (BGR)"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_Vertical,
-                                    tr("LCD (vert. RGB)"));
-  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_Vertical_BGR,
-                                    tr("LCD (vert. BGR)"));
-  antiAliasingLabel->setBuddy(antiAliasingComboBoxx);
-
-  lcdFilterLabel = new QLabel(tr("LCD Filter"));
-  lcdFilterLabel->setAlignment(Qt::AlignRight);
-  lcdFilterComboBox = new QComboBox;
-  lcdFilterComboBox->insertItem(LCDFilter_Default, tr("Default"));
-  lcdFilterComboBox->insertItem(LCDFilter_Light, tr("Light"));
-  lcdFilterComboBox->insertItem(LCDFilter_None, tr("None"));
-  lcdFilterComboBox->insertItem(LCDFilter_Legacy, tr("Legacy"));
-  lcdFilterLabel->setBuddy(lcdFilterComboBox);
-
-  int width;
-  // make all labels have the same width
-  width = hintingModeLabel->minimumSizeHint().width();
-  width = qMax(antiAliasingLabel->minimumSizeHint().width(), width);
-  width = qMax(lcdFilterLabel->minimumSizeHint().width(), width);
-  hintingModeLabel->setMinimumWidth(width);
-  antiAliasingLabel->setMinimumWidth(width);
-  lcdFilterLabel->setMinimumWidth(width);
-
-  // ensure that all items in combo boxes fit completely;
-  // also make all combo boxes have the same width
-  width = hintingModeComboBoxx->minimumSizeHint().width();
-  width = qMax(antiAliasingComboBoxx->minimumSizeHint().width(), width);
-  width = qMax(lcdFilterComboBox->minimumSizeHint().width(), width);
-  hintingModeComboBoxx->setMinimumWidth(width);
-  antiAliasingComboBoxx->setMinimumWidth(width);
-  lcdFilterComboBox->setMinimumWidth(width);
-
-  gammaLabel = new QLabel(tr("Gamma"));
-  gammaLabel->setAlignment(Qt::AlignRight);
-  gammaSlider = new QSlider(Qt::Horizontal);
-  gammaSlider->setRange(0, 30); // in 1/10th
-  gammaSlider->setTickPosition(QSlider::TicksBelow);
-  gammaSlider->setTickInterval(5);
-  gammaLabel->setBuddy(gammaSlider);
-
-  showBitmapCheckBox = new QCheckBox(tr("Show Bitmap"));
-  showPointsCheckBox = new QCheckBox(tr("Show Points"));
-  showPointNumbersCheckBox = new QCheckBox(tr("Show Point Numbers"));
-  showOutlinesCheckBox = new QCheckBox(tr("Show Outlines"));
-
-  infoLeftLayout = new QHBoxLayout;
-  infoLeftLayout->addWidget(fontFilenameLabel);
-
-  hintingModeLayout = new QHBoxLayout;
-  hintingModeLayout->addWidget(hintingModeLabel);
-  hintingModeLayout->addWidget(hintingModeComboBoxx);
-
-  horizontalHintingLayout = new QHBoxLayout;
-  horizontalHintingLayout->addSpacing(20); // XXX px
-  horizontalHintingLayout->addWidget(horizontalHintingCheckBox);
-
-  verticalHintingLayout = new QHBoxLayout;
-  verticalHintingLayout->addSpacing(20); // XXX px
-  verticalHintingLayout->addWidget(verticalHintingCheckBox);
-
-  blueZoneHintingLayout = new QHBoxLayout;
-  blueZoneHintingLayout->addSpacing(20); // XXX px
-  blueZoneHintingLayout->addWidget(blueZoneHintingCheckBox);
-
-  segmentDrawingLayout = new QHBoxLayout;
-  segmentDrawingLayout->addSpacing(20); // XXX px
-  segmentDrawingLayout->addWidget(segmentDrawingCheckBox);
-
-  warpingLayout = new QHBoxLayout;
-  warpingLayout->addSpacing(20); // XXX px
-  warpingLayout->addWidget(warpingCheckBox);
-
-  antiAliasingLayout = new QHBoxLayout;
-  antiAliasingLayout->addWidget(antiAliasingLabel);
-  antiAliasingLayout->addWidget(antiAliasingComboBoxx);
-
-  lcdFilterLayout = new QHBoxLayout;
-  lcdFilterLayout->addWidget(lcdFilterLabel);
-  lcdFilterLayout->addWidget(lcdFilterComboBox);
-
-  gammaLayout = new QHBoxLayout;
-  gammaLayout->addWidget(gammaLabel);
-  gammaLayout->addWidget(gammaSlider);
-
-  pointNumbersLayout = new QHBoxLayout;
-  pointNumbersLayout->addSpacing(20); // XXX px
-  pointNumbersLayout->addWidget(showPointNumbersCheckBox);
-
-  generalTabLayout = new QVBoxLayout;
-  generalTabLayout->addWidget(hintingCheckBox);
-  generalTabLayout->addLayout(hintingModeLayout);
-  generalTabLayout->addWidget(autoHintingCheckBox);
-  generalTabLayout->addLayout(horizontalHintingLayout);
-  generalTabLayout->addLayout(verticalHintingLayout);
-  generalTabLayout->addLayout(blueZoneHintingLayout);
-  generalTabLayout->addLayout(segmentDrawingLayout);
-  generalTabLayout->addLayout(warpingLayout);
-  generalTabLayout->addSpacing(20); // XXX px
-  generalTabLayout->addStretch(1);
-  generalTabLayout->addLayout(antiAliasingLayout);
-  generalTabLayout->addLayout(lcdFilterLayout);
-  generalTabLayout->addSpacing(20); // XXX px
-  generalTabLayout->addStretch(1);
-  generalTabLayout->addLayout(gammaLayout);
-  generalTabLayout->addSpacing(20); // XXX px
-  generalTabLayout->addStretch(1);
-  generalTabLayout->addWidget(showBitmapCheckBox);
-  generalTabLayout->addWidget(showPointsCheckBox);
-  generalTabLayout->addLayout(pointNumbersLayout);
-  generalTabLayout->addWidget(showOutlinesCheckBox);
-
-  generalTabWidget = new QWidget;
-  generalTabWidget->setLayout(generalTabLayout);
-
-  mmgxTabWidget = new QWidget;
-
-  tabWidget = new QTabWidget;
-  tabWidget->addTab(generalTabWidget, tr("General"));
-  tabWidget->addTab(mmgxTabWidget, tr("MM/GX"));
-
-  leftLayout = new QVBoxLayout;
-  leftLayout->addLayout(infoLeftLayout);
-  leftLayout->addWidget(tabWidget);
-
-  // we don't want to expand the left side horizontally;
-  // to change the policy we have to use a widget wrapper
-  leftWidget = new QWidget;
-  leftWidget->setLayout(leftLayout);
-
-  QSizePolicy leftWidgetPolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
-  leftWidgetPolicy.setHorizontalStretch(0);
-  
leftWidgetPolicy.setVerticalPolicy(leftWidget->sizePolicy().verticalPolicy());
-  
leftWidgetPolicy.setHeightForWidth(leftWidget->sizePolicy().hasHeightForWidth());
-
-  leftWidget->setSizePolicy(leftWidgetPolicy);
-
-  // right side
-  glyphIndexLabel = new QLabel;
-  glyphNameLabel = new QLabel;
-  fontNameLabel = new QLabel;
-
-  glyphScene = new QGraphicsScene;
-  glyphScene->addItem(new Grid(gridPen, axisPen));
-
-  currentGlyphBitmapItem = NULL;
-  currentGlyphOutlineItem = NULL;
-  currentGlyphPointsItem = NULL;
-  currentGlyphPointNumbersItem = NULL;
-  drawGlyph();
-
-  glyphView = new QGraphicsViewx;
-  glyphView->setRenderHint(QPainter::Antialiasing, true);
-  glyphView->setDragMode(QGraphicsView::ScrollHandDrag);
-  glyphView->setOptimizationFlags(QGraphicsView::DontSavePainterState);
-  glyphView->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
-  glyphView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
-  glyphView->setScene(glyphScene);
-
-  sizeLabel = new QLabel(tr("Size "));
-  sizeLabel->setAlignment(Qt::AlignRight);
-  sizeDoubleSpinBox = new QDoubleSpinBox;
-  sizeDoubleSpinBox->setAlignment(Qt::AlignRight);
-  sizeDoubleSpinBox->setDecimals(1);
-  sizeDoubleSpinBox->setRange(1, 500);
-  sizeLabel->setBuddy(sizeDoubleSpinBox);
-
-  unitsComboBox = new QComboBox;
-  unitsComboBox->insertItem(Units_px, "px");
-  unitsComboBox->insertItem(Units_pt, "pt");
-
-  dpiLabel = new QLabel(tr("DPI "));
-  dpiLabel->setAlignment(Qt::AlignRight);
-  dpiSpinBox = new QSpinBox;
-  dpiSpinBox->setAlignment(Qt::AlignRight);
-  dpiSpinBox->setRange(10, 600);
-  dpiLabel->setBuddy(dpiSpinBox);
-
-  toStartButtonx = new QPushButtonx("|<");
-  toM1000Buttonx = new QPushButtonx("-1000");
-  toM100Buttonx = new QPushButtonx("-100");
-  toM10Buttonx = new QPushButtonx("-10");
-  toM1Buttonx = new QPushButtonx("-1");
-  toP1Buttonx = new QPushButtonx("+1");
-  toP10Buttonx = new QPushButtonx("+10");
-  toP100Buttonx = new QPushButtonx("+100");
-  toP1000Buttonx = new QPushButtonx("+1000");
-  toEndButtonx = new QPushButtonx(">|");
-
-  zoomLabel = new QLabel(tr("Zoom Factor"));
-  zoomLabel->setAlignment(Qt::AlignRight);
-  zoomSpinBox = new QSpinBoxx;
-  zoomSpinBox->setAlignment(Qt::AlignRight);
-  zoomSpinBox->setRange(1, 1000 - 1000 % 64);
-  zoomSpinBox->setKeyboardTracking(false);
-  zoomLabel->setBuddy(zoomSpinBox);
-
-  previousFontButton = new QPushButton(tr("Previous Font"));
-  nextFontButton = new QPushButton(tr("Next Font"));
-  previousFaceButton = new QPushButton(tr("Previous Face"));
-  nextFaceButton = new QPushButton(tr("Next Face"));
-  previousNamedInstanceButton = new QPushButton(tr("Previous Named Instance"));
-  nextNamedInstanceButton = new QPushButton(tr("Next Named Instance"));
-
-  infoRightLayout = new QGridLayout;
-  infoRightLayout->addWidget(glyphIndexLabel, 0, 0);
-  infoRightLayout->addWidget(glyphNameLabel, 0, 1);
-  infoRightLayout->addWidget(fontNameLabel, 0, 2);
-
-  navigationLayout = new QHBoxLayout;
-  navigationLayout->setSpacing(0);
-  navigationLayout->addStretch(1);
-  navigationLayout->addWidget(toStartButtonx);
-  navigationLayout->addWidget(toM1000Buttonx);
-  navigationLayout->addWidget(toM100Buttonx);
-  navigationLayout->addWidget(toM10Buttonx);
-  navigationLayout->addWidget(toM1Buttonx);
-  navigationLayout->addWidget(toP1Buttonx);
-  navigationLayout->addWidget(toP10Buttonx);
-  navigationLayout->addWidget(toP100Buttonx);
-  navigationLayout->addWidget(toP1000Buttonx);
-  navigationLayout->addWidget(toEndButtonx);
-  navigationLayout->addStretch(1);
-
-  sizeLayout = new QHBoxLayout;
-  sizeLayout->addStretch(2);
-  sizeLayout->addWidget(sizeLabel);
-  sizeLayout->addWidget(sizeDoubleSpinBox);
-  sizeLayout->addWidget(unitsComboBox);
-  sizeLayout->addStretch(1);
-  sizeLayout->addWidget(dpiLabel);
-  sizeLayout->addWidget(dpiSpinBox);
-  sizeLayout->addStretch(1);
-  sizeLayout->addWidget(zoomLabel);
-  sizeLayout->addWidget(zoomSpinBox);
-  sizeLayout->addStretch(2);
-
-  fontLayout = new QGridLayout;
-  fontLayout->setColumnStretch(0, 2);
-  fontLayout->addWidget(nextFontButton, 0, 1);
-  fontLayout->addWidget(previousFontButton, 1, 1);
-  fontLayout->setColumnStretch(2, 1);
-  fontLayout->addWidget(nextFaceButton, 0, 3);
-  fontLayout->addWidget(previousFaceButton, 1, 3);
-  fontLayout->setColumnStretch(4, 1);
-  fontLayout->addWidget(nextNamedInstanceButton, 0, 5);
-  fontLayout->addWidget(previousNamedInstanceButton, 1, 5);
-  fontLayout->setColumnStretch(6, 2);
-
-  rightLayout = new QVBoxLayout;
-  rightLayout->addLayout(infoRightLayout);
-  rightLayout->addWidget(glyphView);
-  rightLayout->addLayout(navigationLayout);
-  rightLayout->addSpacing(10); // XXX px
-  rightLayout->addLayout(sizeLayout);
-  rightLayout->addSpacing(10); // XXX px
-  rightLayout->addLayout(fontLayout);
-
-  // for symmetry with the left side use a widget also
-  rightWidget = new QWidget;
-  rightWidget->setLayout(rightLayout);
-
-  // the whole thing
-  ftinspectLayout = new QHBoxLayout;
-  ftinspectLayout->addWidget(leftWidget);
-  ftinspectLayout->addWidget(rightWidget);
-
-  ftinspectWidget = new QWidget;
-  ftinspectWidget->setLayout(ftinspectLayout);
-  setCentralWidget(ftinspectWidget);
-  setWindowTitle("ftinspect");
-}
-
-
-void
-MainGUI::createConnections()
-{
-  connect(hintingCheckBox, SIGNAL(clicked()),
-          SLOT(checkHinting()));
-
-  connect(hintingModeComboBoxx, SIGNAL(currentIndexChanged(int)),
-          SLOT(checkHintingMode()));
-  connect(antiAliasingComboBoxx, SIGNAL(currentIndexChanged(int)),
-          SLOT(checkAntiAliasing()));
-  connect(lcdFilterComboBox, SIGNAL(currentIndexChanged(int)),
-          SLOT(checkLcdFilter()));
-
-  connect(autoHintingCheckBox, SIGNAL(clicked()),
-          SLOT(checkAutoHinting()));
-  connect(showBitmapCheckBox, SIGNAL(clicked()),
-          SLOT(drawGlyph()));
-  connect(showPointsCheckBox, SIGNAL(clicked()),
-          SLOT(checkShowPoints()));
-  connect(showPointNumbersCheckBox, SIGNAL(clicked()),
-          SLOT(drawGlyph()));
-  connect(showOutlinesCheckBox, SIGNAL(clicked()),
-          SLOT(drawGlyph()));
-
-  connect(sizeDoubleSpinBox, SIGNAL(valueChanged(double)),
-          SLOT(drawGlyph()));
-  connect(unitsComboBox, SIGNAL(currentIndexChanged(int)),
-          SLOT(checkUnits()));
-  connect(dpiSpinBox, SIGNAL(valueChanged(int)),
-          SLOT(drawGlyph()));
-
-  connect(zoomSpinBox, SIGNAL(valueChanged(int)),
-          SLOT(zoom()));
-
-  connect(previousFontButton, SIGNAL(clicked()),
-          SLOT(previousFont()));
-  connect(nextFontButton, SIGNAL(clicked()),
-          SLOT(nextFont()));
-  connect(previousFaceButton, SIGNAL(clicked()),
-          SLOT(previousFace()));
-  connect(nextFaceButton, SIGNAL(clicked()),
-          SLOT(nextFace()));
-  connect(previousNamedInstanceButton, SIGNAL(clicked()),
-          SLOT(previousNamedInstance()));
-  connect(nextNamedInstanceButton, SIGNAL(clicked()),
-          SLOT(nextNamedInstance()));
-
-  glyphNavigationMapper = new QSignalMapper;
-  connect(glyphNavigationMapper, SIGNAL(mapped(int)),
-          SLOT(adjustGlyphIndex(int)));
-
-  connect(toStartButtonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toM1000Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toM100Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toM10Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toM1Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toP1Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toP10Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toP100Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toP1000Buttonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-  connect(toEndButtonx, SIGNAL(clicked()),
-          glyphNavigationMapper, SLOT(map()));
-
-  glyphNavigationMapper->setMapping(toStartButtonx, -0x10000);
-  glyphNavigationMapper->setMapping(toM1000Buttonx, -1000);
-  glyphNavigationMapper->setMapping(toM100Buttonx, -100);
-  glyphNavigationMapper->setMapping(toM10Buttonx, -10);
-  glyphNavigationMapper->setMapping(toM1Buttonx, -1);
-  glyphNavigationMapper->setMapping(toP1Buttonx, 1);
-  glyphNavigationMapper->setMapping(toP10Buttonx, 10);
-  glyphNavigationMapper->setMapping(toP100Buttonx, 100);
-  glyphNavigationMapper->setMapping(toP1000Buttonx, 1000);
-  glyphNavigationMapper->setMapping(toEndButtonx, 0x10000);
-
-  connect(fontWatcher, SIGNAL(fileChanged(const QString&)),
-          SLOT(watchCurrentFont()));
-  connect(timer, SIGNAL(timeout()),
-          SLOT(watchCurrentFont()));
-}
-
-
-void
-MainGUI::createActions()
-{
-  loadFontsAct = new QAction(tr("&Load Fonts"), this);
-  loadFontsAct->setShortcuts(QKeySequence::Open);
-  connect(loadFontsAct, SIGNAL(triggered()), SLOT(loadFonts()));
-
-  closeFontAct = new QAction(tr("&Close Font"), this);
-  closeFontAct->setShortcuts(QKeySequence::Close);
-  connect(closeFontAct, SIGNAL(triggered()), SLOT(closeFont()));
-
-  exitAct = new QAction(tr("E&xit"), this);
-  exitAct->setShortcuts(QKeySequence::Quit);
-  connect(exitAct, SIGNAL(triggered()), SLOT(close()));
-
-  aboutAct = new QAction(tr("&About"), this);
-  connect(aboutAct, SIGNAL(triggered()), SLOT(about()));
-
-  aboutQtAct = new QAction(tr("About &Qt"), this);
-  connect(aboutQtAct, SIGNAL(triggered()), SLOT(aboutQt()));
-}
-
-
-void
-MainGUI::createMenus()
-{
-  menuFile = menuBar()->addMenu(tr("&File"));
-  menuFile->addAction(loadFontsAct);
-  menuFile->addAction(closeFontAct);
-  menuFile->addAction(exitAct);
-
-  menuHelp = menuBar()->addMenu(tr("&Help"));
-  menuHelp->addAction(aboutAct);
-  menuHelp->addAction(aboutQtAct);
-}
-
-
-void
-MainGUI::createStatusBar()
-{
-  statusBar()->showMessage("");
-}
-
-
-void
-MainGUI::clearStatusBar()
-{
-  statusBar()->clearMessage();
-  statusBar()->setStyleSheet("");
-}
-
-
-void
-MainGUI::setDefaults()
-{
-  // set up mappings between property values and combo box indices
-  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_35] = 
HintingMode_TrueType_v35;
-  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_38] = 
HintingMode_TrueType_v38;
-  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_40] = 
HintingMode_TrueType_v40;
-
-  hintingModesCFFHash[FT_CFF_HINTING_FREETYPE] = HintingMode_CFF_FreeType;
-  hintingModesCFFHash[FT_CFF_HINTING_ADOBE] = HintingMode_CFF_Adobe;
-
-  lcdFilterHash[FT_LCD_FILTER_DEFAULT] = LCDFilter_Default;
-  lcdFilterHash[FT_LCD_FILTER_LIGHT] = LCDFilter_Light;
-  lcdFilterHash[FT_LCD_FILTER_NONE] = LCDFilter_None;
-  lcdFilterHash[FT_LCD_FILTER_LEGACY] = LCDFilter_Legacy;
-
-  // make copies and remove existing elements...
-  QHash<int, int> hmTTHash = hintingModesTrueTypeHash;
-  if (hmTTHash.contains(engine->ttInterpreterVersionDefault))
-    hmTTHash.remove(engine->ttInterpreterVersionDefault);
-  if (hmTTHash.contains(engine->ttInterpreterVersionOther))
-    hmTTHash.remove(engine->ttInterpreterVersionOther);
-  if (hmTTHash.contains(engine->ttInterpreterVersionOther1))
-    hmTTHash.remove(engine->ttInterpreterVersionOther1);
-
-  QHash<int, int> hmCFFHash = hintingModesCFFHash;
-  if (hmCFFHash.contains(engine->cffHintingEngineDefault))
-    hmCFFHash.remove(engine->cffHintingEngineDefault);
-  if (hmCFFHash.contains(engine->cffHintingEngineOther))
-    hmCFFHash.remove(engine->cffHintingEngineOther);
-
-  // ... to construct a list of always disabled hinting mode combo box items
-  hintingModesAlwaysDisabled = hmTTHash.values();
-  hintingModesAlwaysDisabled += hmCFFHash.values();
-
-  for (int i = 0; i < hintingModesAlwaysDisabled.size(); i++)
-    hintingModeComboBoxx->setItemEnabled(hintingModesAlwaysDisabled[i],
-                                         false);
-
-  // the next four values always non-negative
-  currentFontIndex = 0;
-  currentFaceIndex = 0;
-  currentNamedInstanceIndex = 0;
-  currentGlyphIndex = 0;
-
-  currentCFFHintingMode
-    = hintingModesCFFHash[engine->cffHintingEngineDefault];
-  currentTTInterpreterVersion
-    = hintingModesTrueTypeHash[engine->ttInterpreterVersionDefault];
-
-  hintingCheckBox->setChecked(true);
-
-  antiAliasingComboBoxx->setCurrentIndex(AntiAliasing_Normal);
-  lcdFilterComboBox->setCurrentIndex(LCDFilter_Light);
-
-  horizontalHintingCheckBox->setChecked(true);
-  verticalHintingCheckBox->setChecked(true);
-  blueZoneHintingCheckBox->setChecked(true);
-
-  showBitmapCheckBox->setChecked(true);
-  showOutlinesCheckBox->setChecked(true);
-
-  gammaSlider->setValue(18); // 1.8
-  sizeDoubleSpinBox->setValue(20);
-  dpiSpinBox->setValue(96);
-  zoomSpinBox->setValue(20);
-
-  checkHinting();
-  checkHintingMode();
-  checkAutoHinting();
-  checkAntiAliasing();
-  checkLcdFilter();
-  checkShowPoints();
-  checkUnits();
-  checkCurrentFontIndex();
-  checkCurrentFaceIndex();
-  checkCurrentNamedInstanceIndex();
-  adjustGlyphIndex(0);
-  zoom();
-}
-
-
-void
-MainGUI::readSettings()
-{
-  QSettings settings;
-//  QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
-//  QSize size = settings.value("size", QSize(400, 400)).toSize();
-//  resize(size);
-//  move(pos);
-}
-
-
-void
-MainGUI::writeSettings()
-{
-  QSettings settings;
-//  settings.setValue("pos", pos());
-//  settings.setValue("size", size());
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// QGraphicsViewx
-//
-/////////////////////////////////////////////////////////////////////////////
-
-QGraphicsViewx::QGraphicsViewx()
-: lastBottomLeftPointInitialized(false)
-{
-  // empty
-}
-
-
-void
-QGraphicsViewx::scrollContentsBy(int dx,
-                                 int dy)
-{
-  QGraphicsView::scrollContentsBy(dx, dy);
-  lastBottomLeftPoint = viewport()->rect().bottomLeft();
-}
-
-
-void
-QGraphicsViewx::resizeEvent(QResizeEvent* event)
-{
-  QGraphicsView::resizeEvent(event);
-
-  // XXX I don't know how to properly initialize this value,
-  //     thus the hack with the boolean
-  if (!lastBottomLeftPointInitialized)
-  {
-    lastBottomLeftPoint = viewport()->rect().bottomLeft();
-    lastBottomLeftPointInitialized = true;
-  }
-
-  QPointF currentBottomLeftPoint = viewport()->rect().bottomLeft();
-  int verticalPosition = verticalScrollBar()->value();
-  verticalScrollBar()->setValue(static_cast<int>(
-                                  verticalPosition
-                                  - (currentBottomLeftPoint.y()
-                                     - lastBottomLeftPoint.y())));
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// QSpinBoxx
-//
-/////////////////////////////////////////////////////////////////////////////
-
-// we want to mark the center of a pixel square with a single dot or a small
-// cross; starting with a certain magnification we thus only use even values
-// so that we can do that symmetrically
-
-int
-QSpinBoxx::valueFromText(const QString& text) const
-{
-  int val = QSpinBox::valueFromText(text);
-
-  if (val > 640)
-    val = val - (val % 64);
-  else if (val > 320)
-    val = val - (val % 32);
-  else if (val > 160)
-    val = val - (val % 16);
-  else if (val > 80)
-    val = val - (val % 8);
-  else if (val > 40)
-    val = val - (val % 4);
-  else if (val > 20)
-    val = val - (val % 2);
-
-  return val;
-}
-
-
-void
-QSpinBoxx::stepBy(int steps)
-{
-  int val = value();
-
-  if (steps > 0)
-  {
-    for (int i = 0; i < steps; i++)
-    {
-      if (val >= 640)
-        val = val + 64;
-      else if (val >= 320)
-        val = val + 32;
-      else if (val >= 160)
-        val = val + 16;
-      else if (val >= 80)
-        val = val + 8;
-      else if (val >= 40)
-        val = val + 4;
-      else if (val >= 20)
-        val = val + 2;
-      else
-        val++;
-    }
-  }
-  else if (steps < 0)
-  {
-    for (int i = 0; i < -steps; i++)
-    {
-      if (val > 640)
-        val = val - 64;
-      else if (val > 320)
-        val = val - 32;
-      else if (val > 160)
-        val = val - 16;
-      else if (val > 80)
-        val = val - 8;
-      else if (val > 40)
-        val = val - 4;
-      else if (val > 20)
-        val = val - 2;
-      else
-        val--;
-    }
-  }
-
-  setValue(val);
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// QComboBoxx
-//
-/////////////////////////////////////////////////////////////////////////////
-
-void
-QComboBoxx::setItemEnabled(int index,
-                           bool enable)
-{
-  const QStandardItemModel* itemModel =
-    qobject_cast<const QStandardItemModel*>(model());
-  QStandardItem* item = itemModel->item(index);
-
-  if (enable)
-  {
-    item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
-    item->setData(QVariant(),
-                  Qt::TextColorRole);
-  }
-  else
-  {
-    item->setFlags(item->flags()
-                   & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
-    // clear item data in order to use default color;
-    // this visually greys out the item
-    item->setData(palette().color(QPalette::Disabled, QPalette::Text),
-                  Qt::TextColorRole);
-  }
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// QPushButtonx
-//
-/////////////////////////////////////////////////////////////////////////////
-
-// code derived from Qt 4.8.7, function `QPushButton::sizeHint',
-// file `src/gui/widgets/qpushbutton.cpp'
-
-QPushButtonx::QPushButtonx(const QString &text,
-                           QWidget *parent)
-: QPushButton(text, parent)
-{
-  QStyleOptionButton opt;
-  opt.initFrom(this);
-  QString s(this->text());
-  QFontMetrics fm = fontMetrics();
-  QSize sz = fm.size(Qt::TextShowMnemonic, s);
-  opt.rect.setSize(sz);
-
-  sz = style()->sizeFromContents(QStyle::CT_PushButton,
-                                 &opt,
-                                 sz,
-                                 this);
-  setFixedWidth(sz.width());
-}
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// main
-//
-/////////////////////////////////////////////////////////////////////////////
-
-int
-main(int argc,
-     char** argv)
-{
-  QApplication app(argc, argv);
-  app.setApplicationName("ftinspect");
-  app.setApplicationVersion(VERSION);
-  app.setOrganizationName("FreeType");
-  app.setOrganizationDomain("freetype.org");
-
-  MainGUI gui;
-  Engine engine(&gui);
-
-  gui.update(&engine);
-  gui.setDefaults();
-
-  gui.show();
-
-  return app.exec();
-}
-
-
-// end of ftinspect.cpp
diff --git a/src/ftinspect.pro b/src/ftinspect.pro
deleted file mode 100644
index 11c0341..0000000
--- a/src/ftinspect.pro
+++ /dev/null
@@ -1,28 +0,0 @@
-QMAKE_CXXFLAGS += -isystem ../../freetype2/include
-
-# To avoid conflicts with the FreeType version compiled into or used by Qt,
-# we use the static library.
-#
-# You should adapt this to your setup.
-unix|macx {
-  LIBS += ../../freetype2/objs/.libs/libfreetype.a
-
-  CONFIG += link_pkgconfig
-  PKGCONFIG += libpng harfbuzz zlib bzip2
-}
-win32 {
-  LIBS += ../../freetyp2/objs/vc2010/freetype28.lib
-  LIBS += -lpng -lharfbuzz -lz -lbz2 -lm
-}
-
-CONFIG += qt debug
-
-# we need access to internal FreeType header files
-DEFINES += FT2_BUILD_LIBRARY
-
-SOURCES += ftinspect.cpp
-HEADERS += ftinspect.h
-
-TARGET = ftinspect
-
-QT += widgets
diff --git a/src/ftinspect/.gitignore b/src/ftinspect/.gitignore
new file mode 100644
index 0000000..792ca43
--- /dev/null
+++ b/src/ftinspect/.gitignore
@@ -0,0 +1,6 @@
+Makefile
+ftinspect
+*.o
+moc_*.cpp
+moc_*.h
+.qmake.stash
diff --git a/src/ftinspect/engine/engine.cpp b/src/ftinspect/engine/engine.cpp
new file mode 100644
index 0000000..c5271c2
--- /dev/null
+++ b/src/ftinspect/engine/engine.cpp
@@ -0,0 +1,649 @@
+// engine.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "engine.hpp"
+#include "../maingui.hpp"
+
+#include <stdexcept>
+#include <stdint.h>
+
+#include FT_CFF_DRIVER_H
+#include FT_LCD_FILTER_H
+#include FT_TRUETYPE_DRIVER_H
+
+// internal FreeType header files; only available in the source code bundle
+#include FT_INTERNAL_DRIVER_H
+#include FT_INTERNAL_OBJECTS_H
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// FaceID
+//
+/////////////////////////////////////////////////////////////////////////////
+
+FaceID::FaceID()
+: fontIndex(-1),
+  faceIndex(-1),
+  namedInstanceIndex(-1)
+{
+  // empty
+}
+
+
+FaceID::FaceID(int fontIdx,
+               long faceIdx,
+               int namedInstanceIdx)
+: fontIndex(fontIdx),
+  faceIndex(faceIdx),
+  namedInstanceIndex(namedInstanceIdx)
+{
+  // empty
+}
+
+
+bool
+FaceID::operator<(const FaceID& other) const
+{
+  bool ret = false;
+
+  if (fontIndex < other.fontIndex)
+    ret = true;
+  else if (fontIndex == other.fontIndex)
+  {
+    if (faceIndex < other.faceIndex)
+      ret = true;
+    else if (faceIndex == other.faceIndex)
+    {
+      if (namedInstanceIndex < other.namedInstanceIndex)
+        ret = true;
+    }
+  }
+
+  return ret;
+}
+
+
+// The face requester is a function provided by the client application to
+// the cache manager to translate an `abstract' face ID into a real
+// `FT_Face' object.
+//
+// We use a map: `faceID' is the value, and its associated key gives the
+// font, face, and named instance indices.  Getting a key from a value is
+// slow, but this must be done only once, since `faceRequester' is only
+// called if the font is not yet in the cache.
+
+FT_Error
+faceRequester(FTC_FaceID ftcFaceID,
+              FT_Library library,
+              FT_Pointer requestData,
+              FT_Face* faceP)
+{
+  MainGUI* gui = static_cast<MainGUI*>(requestData);
+  // `ftcFaceID' is actually an integer
+  // -> first convert pointer to same-width integer, then discard superfluous
+  //    bits (e.g., on x86_64 where pointers are wider than int)
+  int val = static_cast<int>(reinterpret_cast<intptr_t>(ftcFaceID));
+  // make sure this does not cause information loss
+  Q_ASSERT_X(sizeof(void*) >= sizeof(int),
+             "faceRequester",
+             "Pointer size must be at least the size of int"
+             " in order to treat FTC_FaceID correctly");
+
+  const FaceID& faceID = gui->engine->faceIDMap.key(val);
+
+  // this is the only place where we have to check the validity of the font
+  // index; note that the validity of both the face and named instance index
+  // is checked by FreeType itself
+  if (faceID.fontIndex < 0
+      || faceID.fontIndex >= gui->fontList.size())
+    return FT_Err_Invalid_Argument;
+
+  QString& font = gui->fontList[faceID.fontIndex];
+  long faceIndex = faceID.faceIndex;
+
+  if (faceID.namedInstanceIndex > 0)
+    faceIndex += faceID.namedInstanceIndex << 16;
+
+  return FT_New_Face(library,
+                     qPrintable(font),
+                     faceIndex,
+                     faceP);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Engine
+//
+/////////////////////////////////////////////////////////////////////////////
+
+Engine::Engine(MainGUI* g)
+{
+  gui = g;
+  ftSize = NULL;
+  // we reserve value 0 for the `invalid face ID'
+  faceCounter = 1;
+
+  FT_Error error;
+
+  error = FT_Init_FreeType(&library);
+  if (error)
+  {
+    // XXX error handling
+  }
+
+  error = FTC_Manager_New(library, 0, 0, 0,
+                          faceRequester, gui, &cacheManager);
+  if (error)
+  {
+    // XXX error handling
+  }
+
+  error = FTC_SBitCache_New(cacheManager, &sbitsCache);
+  if (error)
+  {
+    // XXX error handling
+  }
+
+  error = FTC_ImageCache_New(cacheManager, &imageCache);
+  if (error)
+  {
+    // XXX error handling
+  }
+
+  // query engines and check for alternatives
+
+  // CFF
+  error = FT_Property_Get(library,
+                          "cff",
+                          "hinting-engine",
+                          &cffHintingEngineDefault);
+  if (error)
+  {
+    // no CFF engine
+    cffHintingEngineDefault = -1;
+    cffHintingEngineOther = -1;
+  }
+  else
+  {
+    int engines[] =
+    {
+      FT_CFF_HINTING_FREETYPE,
+      FT_CFF_HINTING_ADOBE
+    };
+
+    int i;
+    for (i = 0; i < 2; i++)
+      if (cffHintingEngineDefault == engines[i])
+        break;
+
+    cffHintingEngineOther = engines[(i + 1) % 2];
+
+    error = FT_Property_Set(library,
+                            "cff",
+                            "hinting-engine",
+                            &cffHintingEngineOther);
+    if (error)
+      cffHintingEngineOther = -1;
+
+    // reset
+    FT_Property_Set(library,
+                    "cff",
+                    "hinting-engine",
+                    &cffHintingEngineDefault);
+  }
+
+  // TrueType
+  error = FT_Property_Get(library,
+                          "truetype",
+                          "interpreter-version",
+                          &ttInterpreterVersionDefault);
+  if (error)
+  {
+    // no TrueType engine
+    ttInterpreterVersionDefault = -1;
+    ttInterpreterVersionOther = -1;
+    ttInterpreterVersionOther1 = -1;
+  }
+  else
+  {
+    int interpreters[] =
+    {
+      TT_INTERPRETER_VERSION_35,
+      TT_INTERPRETER_VERSION_38,
+      TT_INTERPRETER_VERSION_40
+    };
+
+    int i;
+    for (i = 0; i < 3; i++)
+      if (ttInterpreterVersionDefault == interpreters[i])
+        break;
+
+    ttInterpreterVersionOther = interpreters[(i + 1) % 3];
+
+    error = FT_Property_Set(library,
+                            "truetype",
+                            "interpreter-version",
+                            &ttInterpreterVersionOther);
+    if (error)
+      ttInterpreterVersionOther = -1;
+
+    ttInterpreterVersionOther1 = interpreters[(i + 2) % 3];
+
+    error = FT_Property_Set(library,
+                            "truetype",
+                            "interpreter-version",
+                            &ttInterpreterVersionOther1);
+    if (error)
+      ttInterpreterVersionOther1 = -1;
+
+    // reset
+    FT_Property_Set(library,
+                    "truetype",
+                    "interpreter-version",
+                    &ttInterpreterVersionDefault);
+  }
+
+  // auto-hinter
+  error = FT_Property_Get(library,
+                          "autofitter",
+                          "warping",
+                          &doWarping);
+  if (error)
+  {
+    // no warping
+    haveWarping = 0;
+    doWarping = 0;
+  }
+  else
+  {
+    haveWarping = 1;
+    doWarping = 0; // we don't do warping by default
+
+    FT_Property_Set(library,
+                    "autofitter",
+                    "warping",
+                    &doWarping);
+  }
+}
+
+
+Engine::~Engine()
+{
+  FTC_Manager_Done(cacheManager);
+  FT_Done_FreeType(library);
+}
+
+
+long
+Engine::numberOfFaces(int fontIndex)
+{
+  FT_Face face;
+  long numFaces = -1;
+
+  // search triplet (fontIndex, 0, 0)
+  FTC_FaceID ftcFaceID = reinterpret_cast<void*>
+                           (faceIDMap.value(FaceID(fontIndex,
+                                                   0,
+                                                   0)));
+  if (ftcFaceID)
+  {
+    // found
+    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
+      numFaces = face->num_faces;
+  }
+  else
+  {
+    // not found; try to load triplet (fontIndex, 0, 0)
+    ftcFaceID = reinterpret_cast<void*>(faceCounter);
+    faceIDMap.insert(FaceID(fontIndex, 0, 0),
+                     faceCounter++);
+
+    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
+      numFaces = face->num_faces;
+    else
+    {
+      faceIDMap.remove(FaceID(fontIndex, 0, 0));
+      faceCounter--;
+    }
+  }
+
+  return numFaces;
+}
+
+
+int
+Engine::numberOfNamedInstances(int fontIndex,
+                               long faceIndex)
+{
+  FT_Face face;
+  // we return `n' named instances plus one;
+  // instance index 0 represents a face without a named instance selected
+  int numNamedInstances = -1;
+
+  // search triplet (fontIndex, faceIndex, 0)
+  FTC_FaceID ftcFaceID = reinterpret_cast<void*>
+                           (faceIDMap.value(FaceID(fontIndex,
+                                                   faceIndex,
+                                                   0)));
+  if (ftcFaceID)
+  {
+    // found
+    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
+      numNamedInstances = static_cast<int>((face->style_flags >> 16) + 1);
+  }
+  else
+  {
+    // not found; try to load triplet (fontIndex, faceIndex, 0)
+    ftcFaceID = reinterpret_cast<void*>(faceCounter);
+    faceIDMap.insert(FaceID(fontIndex, faceIndex, 0),
+                     faceCounter++);
+
+    if (!FTC_Manager_LookupFace(cacheManager, ftcFaceID, &face))
+      numNamedInstances = static_cast<int>((face->style_flags >> 16) + 1);
+    else
+    {
+      faceIDMap.remove(FaceID(fontIndex, faceIndex, 0));
+      faceCounter--;
+    }
+  }
+
+  return numNamedInstances;
+}
+
+
+int
+Engine::loadFont(int fontIndex,
+                 long faceIndex,
+                 int namedInstanceIndex)
+{
+  int numGlyphs = -1;
+  fontType = FontType_Other;
+
+  update();
+
+  // search triplet (fontIndex, faceIndex, namedInstanceIndex)
+  scaler.face_id = reinterpret_cast<void*>
+                     (faceIDMap.value(FaceID(fontIndex,
+                                             faceIndex,
+                                             namedInstanceIndex)));
+  if (scaler.face_id)
+  {
+    // found
+    if (!FTC_Manager_LookupSize(cacheManager, &scaler, &ftSize))
+      numGlyphs = ftSize->face->num_glyphs;
+  }
+  else
+  {
+    // not found; try to load triplet
+    // (fontIndex, faceIndex, namedInstanceIndex)
+    scaler.face_id = reinterpret_cast<void*>(faceCounter);
+    faceIDMap.insert(FaceID(fontIndex,
+                            faceIndex,
+                            namedInstanceIndex),
+                     faceCounter++);
+
+    if (!FTC_Manager_LookupSize(cacheManager, &scaler, &ftSize))
+      numGlyphs = ftSize->face->num_glyphs;
+    else
+    {
+      faceIDMap.remove(FaceID(fontIndex,
+                              faceIndex,
+                              namedInstanceIndex));
+      faceCounter--;
+    }
+  }
+
+  if (numGlyphs < 0)
+  {
+    ftSize = NULL;
+    curFamilyName = QString();
+    curStyleName = QString();
+  }
+  else
+  {
+    curFamilyName = QString(ftSize->face->family_name);
+    curStyleName = QString(ftSize->face->style_name);
+
+    FT_Module module = &ftSize->face->driver->root;
+    const char* moduleName = module->clazz->module_name;
+
+    // XXX cover all available modules
+    if (!strcmp(moduleName, "cff"))
+      fontType = FontType_CFF;
+    else if (!strcmp(moduleName, "truetype"))
+      fontType = FontType_TrueType;
+  }
+
+  return numGlyphs;
+}
+
+
+void
+Engine::removeFont(int fontIndex)
+{
+  // we iterate over all triplets that contain the given font index
+  // and remove them
+  QMap<FaceID, int>::iterator iter
+    = faceIDMap.lowerBound(FaceID(fontIndex, 0, 0));
+
+  for (;;)
+  {
+    if (iter == faceIDMap.end())
+      break;
+
+    FaceID faceID = iter.key();
+    if (faceID.fontIndex != fontIndex)
+      break;
+
+    FTC_FaceID ftcFaceID = reinterpret_cast<void*>(iter.value());
+    FTC_Manager_RemoveFaceID(cacheManager, ftcFaceID);
+
+    iter = faceIDMap.erase(iter);
+  }
+}
+
+
+const QString&
+Engine::currentFamilyName()
+{
+  return curFamilyName;
+}
+
+
+const QString&
+Engine::currentStyleName()
+{
+  return curStyleName;
+}
+
+
+QString
+Engine::glyphName(int index)
+{
+  QString name;
+
+  if (index < 0)
+    throw std::runtime_error("Invalid glyph index");
+
+  if (ftSize && FT_HAS_GLYPH_NAMES(ftSize->face))
+  {
+    char buffer[256];
+    if (!FT_Get_Glyph_Name(ftSize->face,
+                           static_cast<unsigned int>(index),
+                           buffer,
+                           sizeof(buffer)))
+      name = QString(buffer);
+  }
+
+  return name;
+}
+
+
+FT_Outline*
+Engine::loadOutline(int glyphIndex)
+{
+  update();
+
+  if (glyphIndex < 0)
+    throw std::runtime_error("Invalid glyph index");
+
+  FT_Glyph glyph;
+
+  // XXX handle bitmap fonts
+
+  // the `scaler' object is set up by the
+  // `update' and `loadFont' methods
+  if (FTC_ImageCache_LookupScaler(imageCache,
+                                  &scaler,
+                                  loadFlags | FT_LOAD_NO_BITMAP,
+                                  static_cast<unsigned int>(glyphIndex),
+                                  &glyph,
+                                  NULL))
+  {
+    // XXX error handling?
+    return NULL;
+  }
+
+  if (glyph->format != FT_GLYPH_FORMAT_OUTLINE)
+    return NULL;
+
+  FT_OutlineGlyph outlineGlyph = reinterpret_cast<FT_OutlineGlyph>(glyph);
+
+  return &outlineGlyph->outline;
+}
+
+
+void
+Engine::setCFFHintingMode(int mode)
+{
+  int index = gui->hintingModesCFFHash.key(mode);
+
+  FT_Error error = FT_Property_Set(library,
+                                   "cff",
+                                   "hinting-engine",
+                                   &index);
+  if (!error)
+  {
+    // reset the cache
+    FTC_Manager_Reset(cacheManager);
+  }
+}
+
+
+void
+Engine::setTTInterpreterVersion(int mode)
+{
+  int index = gui->hintingModesTrueTypeHash.key(mode);
+
+  FT_Error error = FT_Property_Set(library,
+                                   "truetype",
+                                   "interpreter-version",
+                                   &index);
+  if (!error)
+  {
+    // reset the cache
+    FTC_Manager_Reset(cacheManager);
+  }
+}
+
+
+void
+Engine::update()
+{
+  // Spinbox value cannot become negative
+  dpi = static_cast<unsigned int>(gui->dpiSpinBox->value());
+
+  if (gui->unitsComboBox->currentIndex() == MainGUI::Units_px)
+  {
+    pixelSize = gui->sizeDoubleSpinBox->value();
+    pointSize = pixelSize * 72.0 / dpi;
+  }
+  else
+  {
+    pointSize = gui->sizeDoubleSpinBox->value();
+    pixelSize = pointSize * dpi / 72.0;
+  }
+
+  doHinting = gui->hintingCheckBox->isChecked();
+
+  doAutoHinting = gui->autoHintingCheckBox->isChecked();
+  doHorizontalHinting = gui->horizontalHintingCheckBox->isChecked();
+  doVerticalHinting = gui->verticalHintingCheckBox->isChecked();
+  doBlueZoneHinting = gui->blueZoneHintingCheckBox->isChecked();
+  showSegments = gui->segmentDrawingCheckBox->isChecked();
+  doWarping = gui->warpingCheckBox->isChecked();
+
+  gamma = gui->gammaSlider->value();
+
+  loadFlags = FT_LOAD_DEFAULT;
+  if (doAutoHinting)
+    loadFlags |= FT_LOAD_FORCE_AUTOHINT;
+  loadFlags |= FT_LOAD_NO_BITMAP; // XXX handle bitmap fonts also
+
+  int index = gui->antiAliasingComboBoxx->currentIndex();
+
+  if (doHinting)
+  {
+    unsigned long target;
+
+    if (index == MainGUI::AntiAliasing_None)
+      target = FT_LOAD_TARGET_MONO;
+    else
+    {
+      switch (index)
+      {
+      case MainGUI::AntiAliasing_Light:
+        target = FT_LOAD_TARGET_LIGHT;
+        break;
+
+      case MainGUI::AntiAliasing_LCD:
+      case MainGUI::AntiAliasing_LCD_BGR:
+        target = FT_LOAD_TARGET_LCD;
+        break;
+
+      case MainGUI::AntiAliasing_LCD_Vertical:
+      case MainGUI::AntiAliasing_LCD_Vertical_BGR:
+        target = FT_LOAD_TARGET_LCD_V;
+        break;
+
+      default:
+        target = FT_LOAD_TARGET_NORMAL;
+      }
+    }
+
+    loadFlags |= target;
+  }
+  else
+  {
+    loadFlags |= FT_LOAD_NO_HINTING;
+
+    if (index == MainGUI::AntiAliasing_None)
+      loadFlags |= FT_LOAD_MONOCHROME;
+  }
+
+  // XXX handle color fonts also
+
+  scaler.pixel = 0; // use 26.6 format
+
+  if (gui->unitsComboBox->currentIndex() == MainGUI::Units_px)
+  {
+    scaler.width = static_cast<unsigned int>(pixelSize * 64.0);
+    scaler.height = static_cast<unsigned int>(pixelSize * 64.0);
+    scaler.x_res = 0;
+    scaler.y_res = 0;
+  }
+  else
+  {
+    scaler.width = static_cast<unsigned int>(pointSize * 64.0);
+    scaler.height = static_cast<unsigned int>(pointSize * 64.0);
+    scaler.x_res = dpi;
+    scaler.y_res = dpi;
+  }
+}
+
+
+// end of engine.cpp
diff --git a/src/ftinspect/engine/engine.hpp b/src/ftinspect/engine/engine.hpp
new file mode 100644
index 0000000..175e8db
--- /dev/null
+++ b/src/ftinspect/engine/engine.hpp
@@ -0,0 +1,122 @@
+// engine.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QString>
+#include <QMap>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+#include FT_CACHE_H
+
+
+// This structure maps the (font, face, instance) index triplet to abstract
+// IDs (generated by a running number stored in MainGUI's `faceCounter'
+// member).
+//
+// Qt's `QMap' class needs an implementation of the `<' operator.
+
+struct FaceID
+{
+  int fontIndex;
+  long faceIndex;
+  int namedInstanceIndex;
+
+  FaceID();
+  FaceID(int fontIndex,
+         long faceIndex,
+         int namedInstanceIndex);
+  bool operator<(const FaceID& other) const;
+};
+
+
+class MainGUI;
+
+// FreeType specific data.
+
+class Engine
+{
+public:
+  Engine(MainGUI*);
+  ~Engine();
+
+  const QString& currentFamilyName();
+  const QString& currentStyleName();
+  QString glyphName(int glyphIndex);
+  long numberOfFaces(int fontIndex);
+  int numberOfNamedInstances(int fontIndex,
+                             long faceIndex);
+  int loadFont(int fontIndex,
+               long faceIndex,
+               int namedInstanceIndex); // return number of glyphs
+  FT_Outline* loadOutline(int glyphIndex);
+  void removeFont(int fontIndex);
+  void setCFFHintingMode(int mode);
+  void setTTInterpreterVersion(int version);
+  void update();
+
+  friend class MainGUI;
+  friend FT_Error faceRequester(FTC_FaceID,
+                                FT_Library,
+                                FT_Pointer,
+                                FT_Face*);
+
+  // XXX cover all available modules
+  enum FontType
+  {
+    FontType_CFF,
+    FontType_TrueType,
+    FontType_Other
+  };
+
+private:
+  MainGUI* gui;
+
+  int faceCounter; // a running number used to initialize `faceIDMap'
+  QMap<FaceID, int> faceIDMap;
+
+  QString curFamilyName;
+  QString curStyleName;
+
+  FT_Library library;
+  FTC_Manager cacheManager;
+  FTC_ImageCache imageCache;
+  FTC_SBitCache sbitsCache;
+
+  FTC_ScalerRec scaler;
+  FT_Size ftSize;
+
+  int cffHintingEngineDefault;
+  int cffHintingEngineOther;
+
+  int ttInterpreterVersionDefault;
+  int ttInterpreterVersionOther;
+  int ttInterpreterVersionOther1;
+
+  int fontType;
+
+  int haveWarping;
+
+  double pointSize;
+  double pixelSize;
+  unsigned int dpi;
+
+  bool doHinting;
+  bool doAutoHinting;
+  bool doHorizontalHinting;
+  bool doVerticalHinting;
+  bool doBlueZoneHinting;
+  bool showSegments;
+  bool doWarping;
+
+  double gamma;
+
+  unsigned long loadFlags;
+};
+
+
+// end of engine.hpp
diff --git a/src/ftinspect/ftinspect-build.sh b/src/ftinspect/ftinspect-build.sh
new file mode 100755
index 0000000..40e16ba
--- /dev/null
+++ b/src/ftinspect/ftinspect-build.sh
@@ -0,0 +1,9 @@
+export TRASH_OFF=YES
+
+make clean
+qmake ftinspect-qt4.pro
+make
+
+make clean
+qmake-qt5 -spec linux-clang ftinspect-qt5.pro
+make
diff --git a/src/ftinspect/ftinspect-qt4 b/src/ftinspect/ftinspect-qt4
new file mode 100755
index 0000000..a3c1b48
Binary files /dev/null and b/src/ftinspect/ftinspect-qt4 differ
diff --git a/src/ftinspect/ftinspect-qt4.pro b/src/ftinspect/ftinspect-qt4.pro
new file mode 100644
index 0000000..efa1fab
--- /dev/null
+++ b/src/ftinspect/ftinspect-qt4.pro
@@ -0,0 +1,58 @@
+# ftinspect.pro
+
+QMAKE_CXXFLAGS += -isystem ../../../freetype2.compiled \
+                  -isystem ../../../freetype2/include
+
+# To avoid conflicts with the FreeType version compiled into or used by Qt,
+# we use the static library.
+#
+# You should adapt this to your setup.
+unix|macx {
+  LIBS += ../../../freetype2.compiled/libfreetype.a
+
+  CONFIG += link_pkgconfig
+  PKGCONFIG += libpng harfbuzz zlib bzip2
+}
+win32 {
+  LIBS += ../../../freetyp2.compiled/freetype28.lib
+  LIBS += -lpng -lharfbuzz -lz -lbz2 -lm
+}
+
+CONFIG += qt debug
+
+# we need access to internal FreeType header files
+DEFINES += FT2_BUILD_LIBRARY
+
+SOURCES += \
+  engine/engine.cpp \
+  rendering/glyphbitmap.cpp \
+  rendering/glyphoutline.cpp \
+  rendering/glyphpointnumbers.cpp \
+  rendering/glyphpoints.cpp \
+  rendering/grid.cpp \
+  widgets/qcomboboxx.cpp \
+  widgets/qgraphicsviewx.cpp \
+  widgets/qpushbuttonx.cpp \
+  widgets/qspinboxx.cpp \
+  ftinspect.cpp \
+  maingui.cpp
+
+HEADERS += \
+  engine/engine.hpp \
+  rendering/glyphbitmap.hpp \
+  rendering/glyphoutline.hpp \
+  rendering/glyphpointnumbers.hpp \
+  rendering/glyphpoints.hpp \
+  rendering/grid.hpp \
+  widgets/qcomboboxx.hpp \
+  widgets/qgraphicsviewx.hpp \
+  widgets/qpushbuttonx.hpp \
+  widgets/qspinboxx.hpp \
+  maingui.hpp
+
+TARGET = ftinspect-qt4
+
+QT += widgets
+
+
+# end of ftinpect.pro
diff --git a/src/ftinspect/ftinspect-qt5 b/src/ftinspect/ftinspect-qt5
new file mode 100755
index 0000000..79fb6aa
Binary files /dev/null and b/src/ftinspect/ftinspect-qt5 differ
diff --git a/src/ftinspect/ftinspect-qt5.pro b/src/ftinspect/ftinspect-qt5.pro
new file mode 100644
index 0000000..4ca098a
--- /dev/null
+++ b/src/ftinspect/ftinspect-qt5.pro
@@ -0,0 +1,58 @@
+# ftinspect.pro
+
+QMAKE_CXXFLAGS += -isystem ../../../freetype2.compiled \
+                  -isystem ../../../freetype2/include
+
+# To avoid conflicts with the FreeType version compiled into or used by Qt,
+# we use the static library.
+#
+# You should adapt this to your setup.
+unix|macx {
+  LIBS += ../../../freetype2.compiled/libfreetype.a
+
+  CONFIG += link_pkgconfig
+  PKGCONFIG += libpng harfbuzz zlib bzip2
+}
+win32 {
+  LIBS += ../../../freetyp2.compiled/freetype28.lib
+  LIBS += -lpng -lharfbuzz -lz -lbz2 -lm
+}
+
+CONFIG += qt debug
+
+# we need access to internal FreeType header files
+DEFINES += FT2_BUILD_LIBRARY
+
+SOURCES += \
+  engine/engine.cpp \
+  rendering/glyphbitmap.cpp \
+  rendering/glyphoutline.cpp \
+  rendering/glyphpointnumbers.cpp \
+  rendering/glyphpoints.cpp \
+  rendering/grid.cpp \
+  widgets/qcomboboxx.cpp \
+  widgets/qgraphicsviewx.cpp \
+  widgets/qpushbuttonx.cpp \
+  widgets/qspinboxx.cpp \
+  ftinspect.cpp \
+  maingui.cpp
+
+HEADERS += \
+  engine/engine.hpp \
+  rendering/glyphbitmap.hpp \
+  rendering/glyphoutline.hpp \
+  rendering/glyphpointnumbers.hpp \
+  rendering/glyphpoints.hpp \
+  rendering/grid.hpp \
+  widgets/qcomboboxx.hpp \
+  widgets/qgraphicsviewx.hpp \
+  widgets/qpushbuttonx.hpp \
+  widgets/qspinboxx.hpp \
+  maingui.hpp
+
+TARGET = ftinspect-qt5
+
+QT += widgets
+
+
+# end of ftinpect.pro
diff --git a/src/ftinspect/ftinspect.cpp b/src/ftinspect/ftinspect.cpp
new file mode 100644
index 0000000..5624cf8
--- /dev/null
+++ b/src/ftinspect/ftinspect.cpp
@@ -0,0 +1,36 @@
+// ftinspect.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "maingui.hpp"
+#include "engine/engine.hpp"
+
+#include <QApplication>
+
+#define VERSION "X.Y.Z"
+
+
+int
+main(int argc,
+     char** argv)
+{
+  QApplication app(argc, argv);
+  app.setApplicationName("ftinspect");
+  app.setApplicationVersion(VERSION);
+  app.setOrganizationName("FreeType");
+  app.setOrganizationDomain("freetype.org");
+
+  MainGUI gui;
+  Engine engine(&gui);
+
+  gui.update(&engine);
+  gui.setDefaults();
+
+  gui.show();
+
+  return app.exec();
+}
+
+
+// end of ftinspect.cpp
diff --git a/src/ftinspect/ftinspect.pro b/src/ftinspect/ftinspect.pro
new file mode 100644
index 0000000..8573228
--- /dev/null
+++ b/src/ftinspect/ftinspect.pro
@@ -0,0 +1,57 @@
+# ftinspect.pro
+
+QMAKE_CXXFLAGS += -isystem ../../../freetype2/include
+
+# To avoid conflicts with the FreeType version compiled into or used by Qt,
+# we use the static library.
+#
+# You should adapt this to your setup.
+unix|macx {
+  LIBS += ../../../freetype2/objs/.libs/libfreetype.a
+
+  CONFIG += link_pkgconfig
+  PKGCONFIG += libpng harfbuzz zlib bzip2
+}
+win32 {
+  LIBS += ../../../freetyp2/objs/vc2010/freetype271.lib
+  LIBS += -lpng -lharfbuzz -lz -lbz2 -lm
+}
+
+CONFIG += qt debug
+
+# we need access to internal FreeType header files
+DEFINES += FT2_BUILD_LIBRARY
+
+SOURCES += \
+  engine/engine.cpp \
+  rendering/glyphbitmap.cpp \
+  rendering/glyphoutline.cpp \
+  rendering/glyphpointnumbers.cpp \
+  rendering/glyphpoints.cpp \
+  rendering/grid.cpp \
+  widgets/qcomboboxx.cpp \
+  widgets/qgraphicsviewx.cpp \
+  widgets/qpushbuttonx.cpp \
+  widgets/qspinboxx.cpp \
+  ftinspect.cpp \
+  maingui.cpp
+
+HEADERS += \
+  engine/engine.hpp \
+  rendering/glyphbitmap.hpp \
+  rendering/glyphoutline.hpp \
+  rendering/glyphpointnumbers.hpp \
+  rendering/glyphpoints.hpp \
+  rendering/grid.hpp \
+  widgets/qcomboboxx.hpp \
+  widgets/qgraphicsviewx.hpp \
+  widgets/qpushbuttonx.hpp \
+  widgets/qspinboxx.hpp \
+  maingui.hpp
+
+TARGET = ftinspect
+
+QT += widgets
+
+
+# end of ftinpect.pro
diff --git a/src/ftinspect/maingui.cpp b/src/ftinspect/maingui.cpp
new file mode 100644
index 0000000..8393559
--- /dev/null
+++ b/src/ftinspect/maingui.cpp
@@ -0,0 +1,1277 @@
+// maingui.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "maingui.hpp"
+#include "rendering/grid.hpp"
+
+#include <QApplication>
+#include <QDir>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QSettings>
+
+#include FT_CFF_DRIVER_H
+#include FT_TRUETYPE_DRIVER_H
+
+
+MainGUI::MainGUI()
+{
+  engine = NULL;
+
+  fontWatcher = new QFileSystemWatcher;
+  // if the current input file is invalid we retry once a second to load it
+  timer = new QTimer;
+  timer->setInterval(1000);
+
+  setGraphicsDefaults();
+  createLayout();
+  createConnections();
+  createActions();
+  createMenus();
+  createStatusBar();
+
+  readSettings();
+
+  setUnifiedTitleAndToolBarOnMac(true);
+}
+
+
+MainGUI::~MainGUI()
+{
+  // empty
+}
+
+
+void
+MainGUI::update(Engine* e)
+{
+  engine = e;
+}
+
+
+// overloading
+
+void
+MainGUI::closeEvent(QCloseEvent* event)
+{
+  writeSettings();
+  event->accept();
+}
+
+
+void
+MainGUI::about()
+{
+  QMessageBox::about(
+    this,
+    tr("About ftinspect"),
+    tr("<p>This is <b>ftinspect</b> version %1<br>"
+       " Copyright %2 2016<br>"
+       " by Werner Lemberg <tt>&lt;address@hidden&gt;</tt></p>"
+       ""
+       "<p><b>ftinspect</b> shows how a font gets rendered"
+       " by FreeType, allowing control over virtually"
+       " all rendering parameters.</p>"
+       ""
+       "<p>License:"
+       " <a 
href='http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT'>FreeType"
+       " License (FTL)</a> or"
+       " <a 
href='http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT'>GNU"
+       " GPLv2</a></p>")
+       .arg(QApplication::applicationVersion())
+       .arg(QChar(0xA9)));
+}
+
+
+void
+MainGUI::aboutQt()
+{
+  QApplication::aboutQt();
+}
+
+
+void
+MainGUI::loadFonts()
+{
+  int oldSize = fontList.size();
+
+  QStringList files = QFileDialog::getOpenFileNames(
+                        this,
+                        tr("Load one or more fonts"),
+                        QDir::homePath(),
+                        "",
+                        NULL,
+                        QFileDialog::ReadOnly);
+
+  // XXX sort data, uniquify elements
+  fontList.append(files);
+
+  // if we have new fonts, set the current index to the first new one
+  if (oldSize < fontList.size())
+    currentFontIndex = oldSize;
+
+  showFont();
+}
+
+
+void
+MainGUI::closeFont()
+{
+  if (currentFontIndex < fontList.size())
+  {
+    engine->removeFont(currentFontIndex);
+    fontWatcher->removePath(fontList[currentFontIndex]);
+    fontList.removeAt(currentFontIndex);
+  }
+
+  // show next font after deletion, i.e., retain index if possible
+  if (fontList.size())
+  {
+    if (currentFontIndex >= fontList.size())
+      currentFontIndex = fontList.size() - 1;
+  }
+  else
+    currentFontIndex = 0;
+
+  showFont();
+}
+
+
+void
+MainGUI::watchCurrentFont()
+{
+  timer->stop();
+  showFont();
+}
+
+
+void
+MainGUI::showFont()
+{
+  // we do lazy computation of FT_Face objects
+
+  if (currentFontIndex < fontList.size())
+  {
+    QString& font = fontList[currentFontIndex];
+    QFileInfo fileInfo(font);
+    QString fontName = fileInfo.fileName();
+
+    if (fileInfo.exists())
+    {
+      // Qt's file watcher doesn't handle symlinks;
+      // we thus fall back to polling
+      if (fileInfo.isSymLink())
+      {
+        fontName.prepend("<i>");
+        fontName.append("</i>");
+        timer->start();
+      }
+      else
+        fontWatcher->addPath(font);
+    }
+    else
+    {
+      // On Unix-like systems, the symlink's target gets opened; this
+      // implies that deletion of a symlink doesn't make `engine->loadFont'
+      // fail since it operates on a file handle pointing to the target.
+      // For this reason, we remove the font to enforce a reload.
+      engine->removeFont(currentFontIndex);
+    }
+
+    fontFilenameLabel->setText(fontName);
+  }
+  else
+    fontFilenameLabel->clear();
+
+  currentNumberOfFaces
+    = engine->numberOfFaces(currentFontIndex);
+  currentNumberOfNamedInstances
+    = engine->numberOfNamedInstances(currentFontIndex,
+                                     currentFaceIndex);
+  currentNumberOfGlyphs
+    = engine->loadFont(currentFontIndex,
+                       currentFaceIndex,
+                       currentNamedInstanceIndex);
+
+  if (currentNumberOfGlyphs < 0)
+  {
+    // there might be various reasons why the current
+    // (file, face, instance) triplet is invalid or missing;
+    // we thus start our timer to periodically test
+    // whether the font starts working
+    if (currentFontIndex < fontList.size())
+      timer->start();
+  }
+
+  fontNameLabel->setText(QString("%1 %2")
+                         .arg(engine->currentFamilyName())
+                         .arg(engine->currentStyleName()));
+
+  checkCurrentFontIndex();
+  checkCurrentFaceIndex();
+  checkCurrentNamedInstanceIndex();
+  checkHinting();
+  adjustGlyphIndex(0);
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkHinting()
+{
+  if (hintingCheckBox->isChecked())
+  {
+    if (engine->fontType == Engine::FontType_CFF)
+    {
+      for (int i = 0; i < hintingModeComboBoxx->count(); i++)
+      {
+        if (hintingModesCFFHash.key(i, -1) != -1)
+          hintingModeComboBoxx->setItemEnabled(i, true);
+        else
+          hintingModeComboBoxx->setItemEnabled(i, false);
+      }
+
+      hintingModeComboBoxx->setCurrentIndex(currentCFFHintingMode);
+    }
+    else if (engine->fontType == Engine::FontType_TrueType)
+    {
+      for (int i = 0; i < hintingModeComboBoxx->count(); i++)
+      {
+        if (hintingModesTrueTypeHash.key(i, -1) != -1)
+          hintingModeComboBoxx->setItemEnabled(i, true);
+        else
+          hintingModeComboBoxx->setItemEnabled(i, false);
+      }
+
+      hintingModeComboBoxx->setCurrentIndex(currentTTInterpreterVersion);
+    }
+    else
+    {
+      hintingModeLabel->setEnabled(false);
+      hintingModeComboBoxx->setEnabled(false);
+    }
+
+    for (int i = 0; i < hintingModesAlwaysDisabled.size(); i++)
+      hintingModeComboBoxx->setItemEnabled(hintingModesAlwaysDisabled[i],
+                                           false);
+
+    autoHintingCheckBox->setEnabled(true);
+    checkAutoHinting();
+  }
+  else
+  {
+    hintingModeLabel->setEnabled(false);
+    hintingModeComboBoxx->setEnabled(false);
+
+    autoHintingCheckBox->setEnabled(false);
+    horizontalHintingCheckBox->setEnabled(false);
+    verticalHintingCheckBox->setEnabled(false);
+    blueZoneHintingCheckBox->setEnabled(false);
+    segmentDrawingCheckBox->setEnabled(false);
+    warpingCheckBox->setEnabled(false);
+
+    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, false);
+  }
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkHintingMode()
+{
+  int index = hintingModeComboBoxx->currentIndex();
+
+  if (engine->fontType == Engine::FontType_CFF)
+  {
+    engine->setCFFHintingMode(index);
+    currentCFFHintingMode = index;
+  }
+  else if (engine->fontType == Engine::FontType_TrueType)
+  {
+    engine->setTTInterpreterVersion(index);
+    currentTTInterpreterVersion = index;
+  }
+
+  // this enforces reloading of the font
+  showFont();
+}
+
+
+void
+MainGUI::checkAutoHinting()
+{
+  if (autoHintingCheckBox->isChecked())
+  {
+    hintingModeLabel->setEnabled(false);
+    hintingModeComboBoxx->setEnabled(false);
+
+    horizontalHintingCheckBox->setEnabled(true);
+    verticalHintingCheckBox->setEnabled(true);
+    blueZoneHintingCheckBox->setEnabled(true);
+    segmentDrawingCheckBox->setEnabled(true);
+    if (engine->haveWarping)
+      warpingCheckBox->setEnabled(true);
+
+    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, true);
+  }
+  else
+  {
+    if (engine->fontType == Engine::FontType_CFF
+        || engine->fontType == Engine::FontType_TrueType)
+    {
+      hintingModeLabel->setEnabled(true);
+      hintingModeComboBoxx->setEnabled(true);
+    }
+
+    horizontalHintingCheckBox->setEnabled(false);
+    verticalHintingCheckBox->setEnabled(false);
+    blueZoneHintingCheckBox->setEnabled(false);
+    segmentDrawingCheckBox->setEnabled(false);
+    warpingCheckBox->setEnabled(false);
+
+    antiAliasingComboBoxx->setItemEnabled(AntiAliasing_Light, false);
+
+    if (antiAliasingComboBoxx->currentIndex() == AntiAliasing_Light)
+      antiAliasingComboBoxx->setCurrentIndex(AntiAliasing_Normal);
+  }
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkAntiAliasing()
+{
+  int index = antiAliasingComboBoxx->currentIndex();
+
+  if (index == AntiAliasing_None
+      || index == AntiAliasing_Normal
+      || index == AntiAliasing_Light)
+  {
+    lcdFilterLabel->setEnabled(false);
+    lcdFilterComboBox->setEnabled(false);
+  }
+  else
+  {
+    lcdFilterLabel->setEnabled(true);
+    lcdFilterComboBox->setEnabled(true);
+  }
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkLcdFilter()
+{
+  int index = lcdFilterComboBox->currentIndex();
+  FT_Library_SetLcdFilter(engine->library, lcdFilterHash.key(index));
+}
+
+
+void
+MainGUI::checkShowPoints()
+{
+  if (showPointsCheckBox->isChecked())
+    showPointNumbersCheckBox->setEnabled(true);
+  else
+    showPointNumbersCheckBox->setEnabled(false);
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkUnits()
+{
+  int index = unitsComboBox->currentIndex();
+
+  if (index == Units_px)
+  {
+    dpiLabel->setEnabled(false);
+    dpiSpinBox->setEnabled(false);
+    sizeDoubleSpinBox->setSingleStep(1);
+    sizeDoubleSpinBox->setValue(qRound(sizeDoubleSpinBox->value()));
+  }
+  else
+  {
+    dpiLabel->setEnabled(true);
+    dpiSpinBox->setEnabled(true);
+    sizeDoubleSpinBox->setSingleStep(0.5);
+  }
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::adjustGlyphIndex(int delta)
+{
+  // only adjust current glyph index if we have a valid font
+  if (currentNumberOfGlyphs > 0)
+  {
+    currentGlyphIndex += delta;
+    currentGlyphIndex = qBound(0,
+                               currentGlyphIndex,
+                               currentNumberOfGlyphs - 1);
+  }
+
+  QString upperHex = QString::number(currentGlyphIndex, 16).toUpper();
+  glyphIndexLabel->setText(QString("%1 (0x%2)")
+                                   .arg(currentGlyphIndex)
+                                   .arg(upperHex));
+  glyphNameLabel->setText(engine->glyphName(currentGlyphIndex));
+
+  drawGlyph();
+}
+
+
+void
+MainGUI::checkCurrentFontIndex()
+{
+  if (fontList.size() < 2)
+  {
+    previousFontButton->setEnabled(false);
+    nextFontButton->setEnabled(false);
+  }
+  else if (currentFontIndex == 0)
+  {
+    previousFontButton->setEnabled(false);
+    nextFontButton->setEnabled(true);
+  }
+  else if (currentFontIndex >= fontList.size() - 1)
+  {
+    previousFontButton->setEnabled(true);
+    nextFontButton->setEnabled(false);
+  }
+  else
+  {
+    previousFontButton->setEnabled(true);
+    nextFontButton->setEnabled(true);
+  }
+}
+
+
+void
+MainGUI::checkCurrentFaceIndex()
+{
+  if (currentNumberOfFaces < 2)
+  {
+    previousFaceButton->setEnabled(false);
+    nextFaceButton->setEnabled(false);
+  }
+  else if (currentFaceIndex == 0)
+  {
+    previousFaceButton->setEnabled(false);
+    nextFaceButton->setEnabled(true);
+  }
+  else if (currentFaceIndex >= currentNumberOfFaces - 1)
+  {
+    previousFaceButton->setEnabled(true);
+    nextFaceButton->setEnabled(false);
+  }
+  else
+  {
+    previousFaceButton->setEnabled(true);
+    nextFaceButton->setEnabled(true);
+  }
+}
+
+
+void
+MainGUI::checkCurrentNamedInstanceIndex()
+{
+  if (currentNumberOfNamedInstances < 2)
+  {
+    previousNamedInstanceButton->setEnabled(false);
+    nextNamedInstanceButton->setEnabled(false);
+  }
+  else if (currentNamedInstanceIndex == 0)
+  {
+    previousNamedInstanceButton->setEnabled(false);
+    nextNamedInstanceButton->setEnabled(true);
+  }
+  else if (currentNamedInstanceIndex >= currentNumberOfNamedInstances - 1)
+  {
+    previousNamedInstanceButton->setEnabled(true);
+    nextNamedInstanceButton->setEnabled(false);
+  }
+  else
+  {
+    previousNamedInstanceButton->setEnabled(true);
+    nextNamedInstanceButton->setEnabled(true);
+  }
+}
+
+
+void
+MainGUI::previousFont()
+{
+  if (currentFontIndex > 0)
+  {
+    currentFontIndex--;
+    currentFaceIndex = 0;
+    currentNamedInstanceIndex = 0;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::nextFont()
+{
+  if (currentFontIndex < fontList.size() - 1)
+  {
+    currentFontIndex++;
+    currentFaceIndex = 0;
+    currentNamedInstanceIndex = 0;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::previousFace()
+{
+  if (currentFaceIndex > 0)
+  {
+    currentFaceIndex--;
+    currentNamedInstanceIndex = 0;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::nextFace()
+{
+  if (currentFaceIndex < currentNumberOfFaces - 1)
+  {
+    currentFaceIndex++;
+    currentNamedInstanceIndex = 0;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::previousNamedInstance()
+{
+  if (currentNamedInstanceIndex > 0)
+  {
+    currentNamedInstanceIndex--;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::nextNamedInstance()
+{
+  if (currentNamedInstanceIndex < currentNumberOfNamedInstances - 1)
+  {
+    currentNamedInstanceIndex++;
+    showFont();
+  }
+}
+
+
+void
+MainGUI::zoom()
+{
+  int scale = zoomSpinBox->value();
+
+  QTransform transform;
+  transform.scale(scale, scale);
+
+  // we want horizontal and vertical 1px lines displayed with full pixels;
+  // we thus have to shift the coordinate system accordingly, using a value
+  // that represents 0.5px (i.e., half the 1px line width) after the scaling
+  qreal shift = 0.5 / scale;
+  transform.translate(shift, shift);
+
+  glyphView->setTransform(transform);
+}
+
+
+void
+MainGUI::setGraphicsDefaults()
+{
+  // color tables (with suitable opacity values) for converting
+  // FreeType's pixmaps to something Qt understands
+  monoColorTable.append(QColor(Qt::transparent).rgba());
+  monoColorTable.append(QColor(Qt::black).rgba());
+
+  for (int i = 0xFF; i >= 0; i--)
+    grayColorTable.append(qRgba(i, i, i, 0xFF - i));
+
+  // XXX make this user-configurable
+
+  axisPen.setColor(Qt::black);
+  axisPen.setWidth(0);
+  blueZonePen.setColor(QColor(64, 64, 255, 64)); // light blue
+  blueZonePen.setWidth(0);
+  gridPen.setColor(Qt::lightGray);
+  gridPen.setWidth(0);
+  offPen.setColor(Qt::darkGreen);
+  offPen.setWidth(3);
+  onPen.setColor(Qt::red);
+  onPen.setWidth(3);
+  outlinePen.setColor(Qt::red);
+  outlinePen.setWidth(0);
+  segmentPen.setColor(QColor(64, 255, 128, 64)); // light green
+  segmentPen.setWidth(0);
+}
+
+
+void
+MainGUI::drawGlyph()
+{
+  // the call to `engine->loadOutline' updates FreeType's load flags
+
+  if (!engine)
+    return;
+
+  if (currentGlyphBitmapItem)
+  {
+    glyphScene->removeItem(currentGlyphBitmapItem);
+    delete currentGlyphBitmapItem;
+
+    currentGlyphBitmapItem = NULL;
+  }
+
+  if (currentGlyphOutlineItem)
+  {
+    glyphScene->removeItem(currentGlyphOutlineItem);
+    delete currentGlyphOutlineItem;
+
+    currentGlyphOutlineItem = NULL;
+  }
+
+  if (currentGlyphPointsItem)
+  {
+    glyphScene->removeItem(currentGlyphPointsItem);
+    delete currentGlyphPointsItem;
+
+    currentGlyphPointsItem = NULL;
+  }
+
+  if (currentGlyphPointNumbersItem)
+  {
+    glyphScene->removeItem(currentGlyphPointNumbersItem);
+    delete currentGlyphPointNumbersItem;
+
+    currentGlyphPointNumbersItem = NULL;
+  }
+
+  FT_Outline* outline = engine->loadOutline(currentGlyphIndex);
+  if (outline)
+  {
+    if (showBitmapCheckBox->isChecked())
+    {
+      // XXX support LCD
+      FT_Pixel_Mode pixelMode = FT_PIXEL_MODE_GRAY;
+      if (antiAliasingComboBoxx->currentIndex() == AntiAliasing_None)
+        pixelMode = FT_PIXEL_MODE_MONO;
+
+      currentGlyphBitmapItem = new GlyphBitmap(outline,
+                                               engine->library,
+                                               pixelMode,
+                                               monoColorTable,
+                                               grayColorTable);
+      glyphScene->addItem(currentGlyphBitmapItem);
+    }
+
+    if (showOutlinesCheckBox->isChecked())
+    {
+      currentGlyphOutlineItem = new GlyphOutline(outlinePen, outline);
+      glyphScene->addItem(currentGlyphOutlineItem);
+    }
+
+    if (showPointsCheckBox->isChecked())
+    {
+      currentGlyphPointsItem = new GlyphPoints(onPen, offPen, outline);
+      glyphScene->addItem(currentGlyphPointsItem);
+
+      if (showPointNumbersCheckBox->isChecked())
+      {
+        currentGlyphPointNumbersItem = new GlyphPointNumbers(onPen,
+                                                             offPen,
+                                                             outline);
+        glyphScene->addItem(currentGlyphPointNumbersItem);
+      }
+    }
+  }
+
+  glyphScene->update();
+}
+
+
+// XXX distances are specified in pixels,
+//     making the layout dependent on the output device resolution
+void
+MainGUI::createLayout()
+{
+  // left side
+  fontFilenameLabel = new QLabel;
+
+  hintingCheckBox = new QCheckBox(tr("Hinting"));
+
+  hintingModeLabel = new QLabel(tr("Hinting Mode"));
+  hintingModeLabel->setAlignment(Qt::AlignRight);
+  hintingModeComboBoxx = new QComboBoxx;
+  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v35,
+                                   tr("TrueType v35"));
+  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v38,
+                                   tr("TrueType v38"));
+  hintingModeComboBoxx->insertItem(HintingMode_TrueType_v40,
+                                   tr("TrueType v40"));
+  hintingModeComboBoxx->insertItem(HintingMode_CFF_FreeType,
+                                   tr("CFF (FreeType)"));
+  hintingModeComboBoxx->insertItem(HintingMode_CFF_Adobe,
+                                   tr("CFF (Adobe)"));
+  hintingModeLabel->setBuddy(hintingModeComboBoxx);
+
+  autoHintingCheckBox = new QCheckBox(tr("Auto-Hinting"));
+  horizontalHintingCheckBox = new QCheckBox(tr("Horizontal Hinting"));
+  verticalHintingCheckBox = new QCheckBox(tr("Vertical Hinting"));
+  blueZoneHintingCheckBox = new QCheckBox(tr("Blue-Zone Hinting"));
+  segmentDrawingCheckBox = new QCheckBox(tr("Segment Drawing"));
+  warpingCheckBox = new QCheckBox(tr("Warping"));
+
+  antiAliasingLabel = new QLabel(tr("Anti-Aliasing"));
+  antiAliasingLabel->setAlignment(Qt::AlignRight);
+  antiAliasingComboBoxx = new QComboBoxx;
+  antiAliasingComboBoxx->insertItem(AntiAliasing_None,
+                                    tr("None"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_Normal,
+                                    tr("Normal"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_Light,
+                                    tr("Light"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD,
+                                    tr("LCD (RGB)"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_BGR,
+                                    tr("LCD (BGR)"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_Vertical,
+                                    tr("LCD (vert. RGB)"));
+  antiAliasingComboBoxx->insertItem(AntiAliasing_LCD_Vertical_BGR,
+                                    tr("LCD (vert. BGR)"));
+  antiAliasingLabel->setBuddy(antiAliasingComboBoxx);
+
+  lcdFilterLabel = new QLabel(tr("LCD Filter"));
+  lcdFilterLabel->setAlignment(Qt::AlignRight);
+  lcdFilterComboBox = new QComboBox;
+  lcdFilterComboBox->insertItem(LCDFilter_Default, tr("Default"));
+  lcdFilterComboBox->insertItem(LCDFilter_Light, tr("Light"));
+  lcdFilterComboBox->insertItem(LCDFilter_None, tr("None"));
+  lcdFilterComboBox->insertItem(LCDFilter_Legacy, tr("Legacy"));
+  lcdFilterLabel->setBuddy(lcdFilterComboBox);
+
+  int width;
+  // make all labels have the same width
+  width = hintingModeLabel->minimumSizeHint().width();
+  width = qMax(antiAliasingLabel->minimumSizeHint().width(), width);
+  width = qMax(lcdFilterLabel->minimumSizeHint().width(), width);
+  hintingModeLabel->setMinimumWidth(width);
+  antiAliasingLabel->setMinimumWidth(width);
+  lcdFilterLabel->setMinimumWidth(width);
+
+  // ensure that all items in combo boxes fit completely;
+  // also make all combo boxes have the same width
+  width = hintingModeComboBoxx->minimumSizeHint().width();
+  width = qMax(antiAliasingComboBoxx->minimumSizeHint().width(), width);
+  width = qMax(lcdFilterComboBox->minimumSizeHint().width(), width);
+  hintingModeComboBoxx->setMinimumWidth(width);
+  antiAliasingComboBoxx->setMinimumWidth(width);
+  lcdFilterComboBox->setMinimumWidth(width);
+
+  gammaLabel = new QLabel(tr("Gamma"));
+  gammaLabel->setAlignment(Qt::AlignRight);
+  gammaSlider = new QSlider(Qt::Horizontal);
+  gammaSlider->setRange(0, 30); // in 1/10th
+  gammaSlider->setTickPosition(QSlider::TicksBelow);
+  gammaSlider->setTickInterval(5);
+  gammaLabel->setBuddy(gammaSlider);
+
+  showBitmapCheckBox = new QCheckBox(tr("Show Bitmap"));
+  showPointsCheckBox = new QCheckBox(tr("Show Points"));
+  showPointNumbersCheckBox = new QCheckBox(tr("Show Point Numbers"));
+  showOutlinesCheckBox = new QCheckBox(tr("Show Outlines"));
+
+  infoLeftLayout = new QHBoxLayout;
+  infoLeftLayout->addWidget(fontFilenameLabel);
+
+  hintingModeLayout = new QHBoxLayout;
+  hintingModeLayout->addWidget(hintingModeLabel);
+  hintingModeLayout->addWidget(hintingModeComboBoxx);
+
+  horizontalHintingLayout = new QHBoxLayout;
+  horizontalHintingLayout->addSpacing(20); // XXX px
+  horizontalHintingLayout->addWidget(horizontalHintingCheckBox);
+
+  verticalHintingLayout = new QHBoxLayout;
+  verticalHintingLayout->addSpacing(20); // XXX px
+  verticalHintingLayout->addWidget(verticalHintingCheckBox);
+
+  blueZoneHintingLayout = new QHBoxLayout;
+  blueZoneHintingLayout->addSpacing(20); // XXX px
+  blueZoneHintingLayout->addWidget(blueZoneHintingCheckBox);
+
+  segmentDrawingLayout = new QHBoxLayout;
+  segmentDrawingLayout->addSpacing(20); // XXX px
+  segmentDrawingLayout->addWidget(segmentDrawingCheckBox);
+
+  warpingLayout = new QHBoxLayout;
+  warpingLayout->addSpacing(20); // XXX px
+  warpingLayout->addWidget(warpingCheckBox);
+
+  antiAliasingLayout = new QHBoxLayout;
+  antiAliasingLayout->addWidget(antiAliasingLabel);
+  antiAliasingLayout->addWidget(antiAliasingComboBoxx);
+
+  lcdFilterLayout = new QHBoxLayout;
+  lcdFilterLayout->addWidget(lcdFilterLabel);
+  lcdFilterLayout->addWidget(lcdFilterComboBox);
+
+  gammaLayout = new QHBoxLayout;
+  gammaLayout->addWidget(gammaLabel);
+  gammaLayout->addWidget(gammaSlider);
+
+  pointNumbersLayout = new QHBoxLayout;
+  pointNumbersLayout->addSpacing(20); // XXX px
+  pointNumbersLayout->addWidget(showPointNumbersCheckBox);
+
+  generalTabLayout = new QVBoxLayout;
+  generalTabLayout->addWidget(hintingCheckBox);
+  generalTabLayout->addLayout(hintingModeLayout);
+  generalTabLayout->addWidget(autoHintingCheckBox);
+  generalTabLayout->addLayout(horizontalHintingLayout);
+  generalTabLayout->addLayout(verticalHintingLayout);
+  generalTabLayout->addLayout(blueZoneHintingLayout);
+  generalTabLayout->addLayout(segmentDrawingLayout);
+  generalTabLayout->addLayout(warpingLayout);
+  generalTabLayout->addSpacing(20); // XXX px
+  generalTabLayout->addStretch(1);
+  generalTabLayout->addLayout(antiAliasingLayout);
+  generalTabLayout->addLayout(lcdFilterLayout);
+  generalTabLayout->addSpacing(20); // XXX px
+  generalTabLayout->addStretch(1);
+  generalTabLayout->addLayout(gammaLayout);
+  generalTabLayout->addSpacing(20); // XXX px
+  generalTabLayout->addStretch(1);
+  generalTabLayout->addWidget(showBitmapCheckBox);
+  generalTabLayout->addWidget(showPointsCheckBox);
+  generalTabLayout->addLayout(pointNumbersLayout);
+  generalTabLayout->addWidget(showOutlinesCheckBox);
+
+  generalTabWidget = new QWidget;
+  generalTabWidget->setLayout(generalTabLayout);
+
+  mmgxTabWidget = new QWidget;
+
+  tabWidget = new QTabWidget;
+  tabWidget->addTab(generalTabWidget, tr("General"));
+  tabWidget->addTab(mmgxTabWidget, tr("MM/GX"));
+
+  leftLayout = new QVBoxLayout;
+  leftLayout->addLayout(infoLeftLayout);
+  leftLayout->addWidget(tabWidget);
+
+  // we don't want to expand the left side horizontally;
+  // to change the policy we have to use a widget wrapper
+  leftWidget = new QWidget;
+  leftWidget->setLayout(leftLayout);
+
+  QSizePolicy leftWidgetPolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+  leftWidgetPolicy.setHorizontalStretch(0);
+  
leftWidgetPolicy.setVerticalPolicy(leftWidget->sizePolicy().verticalPolicy());
+  
leftWidgetPolicy.setHeightForWidth(leftWidget->sizePolicy().hasHeightForWidth());
+
+  leftWidget->setSizePolicy(leftWidgetPolicy);
+
+  // right side
+  glyphIndexLabel = new QLabel;
+  glyphNameLabel = new QLabel;
+  fontNameLabel = new QLabel;
+
+  glyphScene = new QGraphicsScene;
+  glyphScene->addItem(new Grid(gridPen, axisPen));
+
+  currentGlyphBitmapItem = NULL;
+  currentGlyphOutlineItem = NULL;
+  currentGlyphPointsItem = NULL;
+  currentGlyphPointNumbersItem = NULL;
+  drawGlyph();
+
+  glyphView = new QGraphicsViewx;
+  glyphView->setRenderHint(QPainter::Antialiasing, true);
+  glyphView->setDragMode(QGraphicsView::ScrollHandDrag);
+  glyphView->setOptimizationFlags(QGraphicsView::DontSavePainterState);
+  glyphView->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
+  glyphView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
+  glyphView->setScene(glyphScene);
+
+  sizeLabel = new QLabel(tr("Size "));
+  sizeLabel->setAlignment(Qt::AlignRight);
+  sizeDoubleSpinBox = new QDoubleSpinBox;
+  sizeDoubleSpinBox->setAlignment(Qt::AlignRight);
+  sizeDoubleSpinBox->setDecimals(1);
+  sizeDoubleSpinBox->setRange(1, 500);
+  sizeLabel->setBuddy(sizeDoubleSpinBox);
+
+  unitsComboBox = new QComboBox;
+  unitsComboBox->insertItem(Units_px, "px");
+  unitsComboBox->insertItem(Units_pt, "pt");
+
+  dpiLabel = new QLabel(tr("DPI "));
+  dpiLabel->setAlignment(Qt::AlignRight);
+  dpiSpinBox = new QSpinBox;
+  dpiSpinBox->setAlignment(Qt::AlignRight);
+  dpiSpinBox->setRange(10, 600);
+  dpiLabel->setBuddy(dpiSpinBox);
+
+  toStartButtonx = new QPushButtonx("|<");
+  toM1000Buttonx = new QPushButtonx("-1000");
+  toM100Buttonx = new QPushButtonx("-100");
+  toM10Buttonx = new QPushButtonx("-10");
+  toM1Buttonx = new QPushButtonx("-1");
+  toP1Buttonx = new QPushButtonx("+1");
+  toP10Buttonx = new QPushButtonx("+10");
+  toP100Buttonx = new QPushButtonx("+100");
+  toP1000Buttonx = new QPushButtonx("+1000");
+  toEndButtonx = new QPushButtonx(">|");
+
+  zoomLabel = new QLabel(tr("Zoom Factor"));
+  zoomLabel->setAlignment(Qt::AlignRight);
+  zoomSpinBox = new QSpinBoxx;
+  zoomSpinBox->setAlignment(Qt::AlignRight);
+  zoomSpinBox->setRange(1, 1000 - 1000 % 64);
+  zoomSpinBox->setKeyboardTracking(false);
+  zoomLabel->setBuddy(zoomSpinBox);
+
+  previousFontButton = new QPushButton(tr("Previous Font"));
+  nextFontButton = new QPushButton(tr("Next Font"));
+  previousFaceButton = new QPushButton(tr("Previous Face"));
+  nextFaceButton = new QPushButton(tr("Next Face"));
+  previousNamedInstanceButton = new QPushButton(tr("Previous Named Instance"));
+  nextNamedInstanceButton = new QPushButton(tr("Next Named Instance"));
+
+  infoRightLayout = new QGridLayout;
+  infoRightLayout->addWidget(glyphIndexLabel, 0, 0);
+  infoRightLayout->addWidget(glyphNameLabel, 0, 1);
+  infoRightLayout->addWidget(fontNameLabel, 0, 2);
+
+  navigationLayout = new QHBoxLayout;
+  navigationLayout->setSpacing(0);
+  navigationLayout->addStretch(1);
+  navigationLayout->addWidget(toStartButtonx);
+  navigationLayout->addWidget(toM1000Buttonx);
+  navigationLayout->addWidget(toM100Buttonx);
+  navigationLayout->addWidget(toM10Buttonx);
+  navigationLayout->addWidget(toM1Buttonx);
+  navigationLayout->addWidget(toP1Buttonx);
+  navigationLayout->addWidget(toP10Buttonx);
+  navigationLayout->addWidget(toP100Buttonx);
+  navigationLayout->addWidget(toP1000Buttonx);
+  navigationLayout->addWidget(toEndButtonx);
+  navigationLayout->addStretch(1);
+
+  sizeLayout = new QHBoxLayout;
+  sizeLayout->addStretch(2);
+  sizeLayout->addWidget(sizeLabel);
+  sizeLayout->addWidget(sizeDoubleSpinBox);
+  sizeLayout->addWidget(unitsComboBox);
+  sizeLayout->addStretch(1);
+  sizeLayout->addWidget(dpiLabel);
+  sizeLayout->addWidget(dpiSpinBox);
+  sizeLayout->addStretch(1);
+  sizeLayout->addWidget(zoomLabel);
+  sizeLayout->addWidget(zoomSpinBox);
+  sizeLayout->addStretch(2);
+
+  fontLayout = new QGridLayout;
+  fontLayout->setColumnStretch(0, 2);
+  fontLayout->addWidget(nextFontButton, 0, 1);
+  fontLayout->addWidget(previousFontButton, 1, 1);
+  fontLayout->setColumnStretch(2, 1);
+  fontLayout->addWidget(nextFaceButton, 0, 3);
+  fontLayout->addWidget(previousFaceButton, 1, 3);
+  fontLayout->setColumnStretch(4, 1);
+  fontLayout->addWidget(nextNamedInstanceButton, 0, 5);
+  fontLayout->addWidget(previousNamedInstanceButton, 1, 5);
+  fontLayout->setColumnStretch(6, 2);
+
+  rightLayout = new QVBoxLayout;
+  rightLayout->addLayout(infoRightLayout);
+  rightLayout->addWidget(glyphView);
+  rightLayout->addLayout(navigationLayout);
+  rightLayout->addSpacing(10); // XXX px
+  rightLayout->addLayout(sizeLayout);
+  rightLayout->addSpacing(10); // XXX px
+  rightLayout->addLayout(fontLayout);
+
+  // for symmetry with the left side use a widget also
+  rightWidget = new QWidget;
+  rightWidget->setLayout(rightLayout);
+
+  // the whole thing
+  ftinspectLayout = new QHBoxLayout;
+  ftinspectLayout->addWidget(leftWidget);
+  ftinspectLayout->addWidget(rightWidget);
+
+  ftinspectWidget = new QWidget;
+  ftinspectWidget->setLayout(ftinspectLayout);
+  setCentralWidget(ftinspectWidget);
+  setWindowTitle("ftinspect");
+}
+
+
+void
+MainGUI::createConnections()
+{
+  connect(hintingCheckBox, SIGNAL(clicked()),
+          SLOT(checkHinting()));
+
+  connect(hintingModeComboBoxx, SIGNAL(currentIndexChanged(int)),
+          SLOT(checkHintingMode()));
+  connect(antiAliasingComboBoxx, SIGNAL(currentIndexChanged(int)),
+          SLOT(checkAntiAliasing()));
+  connect(lcdFilterComboBox, SIGNAL(currentIndexChanged(int)),
+          SLOT(checkLcdFilter()));
+
+  connect(autoHintingCheckBox, SIGNAL(clicked()),
+          SLOT(checkAutoHinting()));
+  connect(showBitmapCheckBox, SIGNAL(clicked()),
+          SLOT(drawGlyph()));
+  connect(showPointsCheckBox, SIGNAL(clicked()),
+          SLOT(checkShowPoints()));
+  connect(showPointNumbersCheckBox, SIGNAL(clicked()),
+          SLOT(drawGlyph()));
+  connect(showOutlinesCheckBox, SIGNAL(clicked()),
+          SLOT(drawGlyph()));
+
+  connect(sizeDoubleSpinBox, SIGNAL(valueChanged(double)),
+          SLOT(drawGlyph()));
+  connect(unitsComboBox, SIGNAL(currentIndexChanged(int)),
+          SLOT(checkUnits()));
+  connect(dpiSpinBox, SIGNAL(valueChanged(int)),
+          SLOT(drawGlyph()));
+
+  connect(zoomSpinBox, SIGNAL(valueChanged(int)),
+          SLOT(zoom()));
+
+  connect(previousFontButton, SIGNAL(clicked()),
+          SLOT(previousFont()));
+  connect(nextFontButton, SIGNAL(clicked()),
+          SLOT(nextFont()));
+  connect(previousFaceButton, SIGNAL(clicked()),
+          SLOT(previousFace()));
+  connect(nextFaceButton, SIGNAL(clicked()),
+          SLOT(nextFace()));
+  connect(previousNamedInstanceButton, SIGNAL(clicked()),
+          SLOT(previousNamedInstance()));
+  connect(nextNamedInstanceButton, SIGNAL(clicked()),
+          SLOT(nextNamedInstance()));
+
+  glyphNavigationMapper = new QSignalMapper;
+  connect(glyphNavigationMapper, SIGNAL(mapped(int)),
+          SLOT(adjustGlyphIndex(int)));
+
+  connect(toStartButtonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toM1000Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toM100Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toM10Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toM1Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toP1Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toP10Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toP100Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toP1000Buttonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+  connect(toEndButtonx, SIGNAL(clicked()),
+          glyphNavigationMapper, SLOT(map()));
+
+  glyphNavigationMapper->setMapping(toStartButtonx, -0x10000);
+  glyphNavigationMapper->setMapping(toM1000Buttonx, -1000);
+  glyphNavigationMapper->setMapping(toM100Buttonx, -100);
+  glyphNavigationMapper->setMapping(toM10Buttonx, -10);
+  glyphNavigationMapper->setMapping(toM1Buttonx, -1);
+  glyphNavigationMapper->setMapping(toP1Buttonx, 1);
+  glyphNavigationMapper->setMapping(toP10Buttonx, 10);
+  glyphNavigationMapper->setMapping(toP100Buttonx, 100);
+  glyphNavigationMapper->setMapping(toP1000Buttonx, 1000);
+  glyphNavigationMapper->setMapping(toEndButtonx, 0x10000);
+
+  connect(fontWatcher, SIGNAL(fileChanged(const QString&)),
+          SLOT(watchCurrentFont()));
+  connect(timer, SIGNAL(timeout()),
+          SLOT(watchCurrentFont()));
+}
+
+
+void
+MainGUI::createActions()
+{
+  loadFontsAct = new QAction(tr("&Load Fonts"), this);
+  loadFontsAct->setShortcuts(QKeySequence::Open);
+  connect(loadFontsAct, SIGNAL(triggered()), SLOT(loadFonts()));
+
+  closeFontAct = new QAction(tr("&Close Font"), this);
+  closeFontAct->setShortcuts(QKeySequence::Close);
+  connect(closeFontAct, SIGNAL(triggered()), SLOT(closeFont()));
+
+  exitAct = new QAction(tr("E&xit"), this);
+  exitAct->setShortcuts(QKeySequence::Quit);
+  connect(exitAct, SIGNAL(triggered()), SLOT(close()));
+
+  aboutAct = new QAction(tr("&About"), this);
+  connect(aboutAct, SIGNAL(triggered()), SLOT(about()));
+
+  aboutQtAct = new QAction(tr("About &Qt"), this);
+  connect(aboutQtAct, SIGNAL(triggered()), SLOT(aboutQt()));
+}
+
+
+void
+MainGUI::createMenus()
+{
+  menuFile = menuBar()->addMenu(tr("&File"));
+  menuFile->addAction(loadFontsAct);
+  menuFile->addAction(closeFontAct);
+  menuFile->addAction(exitAct);
+
+  menuHelp = menuBar()->addMenu(tr("&Help"));
+  menuHelp->addAction(aboutAct);
+  menuHelp->addAction(aboutQtAct);
+}
+
+
+void
+MainGUI::createStatusBar()
+{
+  statusBar()->showMessage("");
+}
+
+
+void
+MainGUI::clearStatusBar()
+{
+  statusBar()->clearMessage();
+  statusBar()->setStyleSheet("");
+}
+
+
+void
+MainGUI::setDefaults()
+{
+  // set up mappings between property values and combo box indices
+  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_35] = 
HintingMode_TrueType_v35;
+  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_38] = 
HintingMode_TrueType_v38;
+  hintingModesTrueTypeHash[TT_INTERPRETER_VERSION_40] = 
HintingMode_TrueType_v40;
+
+  hintingModesCFFHash[FT_CFF_HINTING_FREETYPE] = HintingMode_CFF_FreeType;
+  hintingModesCFFHash[FT_CFF_HINTING_ADOBE] = HintingMode_CFF_Adobe;
+
+  lcdFilterHash[FT_LCD_FILTER_DEFAULT] = LCDFilter_Default;
+  lcdFilterHash[FT_LCD_FILTER_LIGHT] = LCDFilter_Light;
+  lcdFilterHash[FT_LCD_FILTER_NONE] = LCDFilter_None;
+  lcdFilterHash[FT_LCD_FILTER_LEGACY] = LCDFilter_Legacy;
+
+  // make copies and remove existing elements...
+  QHash<int, int> hmTTHash = hintingModesTrueTypeHash;
+  if (hmTTHash.contains(engine->ttInterpreterVersionDefault))
+    hmTTHash.remove(engine->ttInterpreterVersionDefault);
+  if (hmTTHash.contains(engine->ttInterpreterVersionOther))
+    hmTTHash.remove(engine->ttInterpreterVersionOther);
+  if (hmTTHash.contains(engine->ttInterpreterVersionOther1))
+    hmTTHash.remove(engine->ttInterpreterVersionOther1);
+
+  QHash<int, int> hmCFFHash = hintingModesCFFHash;
+  if (hmCFFHash.contains(engine->cffHintingEngineDefault))
+    hmCFFHash.remove(engine->cffHintingEngineDefault);
+  if (hmCFFHash.contains(engine->cffHintingEngineOther))
+    hmCFFHash.remove(engine->cffHintingEngineOther);
+
+  // ... to construct a list of always disabled hinting mode combo box items
+  hintingModesAlwaysDisabled = hmTTHash.values();
+  hintingModesAlwaysDisabled += hmCFFHash.values();
+
+  for (int i = 0; i < hintingModesAlwaysDisabled.size(); i++)
+    hintingModeComboBoxx->setItemEnabled(hintingModesAlwaysDisabled[i],
+                                         false);
+
+  // the next four values always non-negative
+  currentFontIndex = 0;
+  currentFaceIndex = 0;
+  currentNamedInstanceIndex = 0;
+  currentGlyphIndex = 0;
+
+  currentCFFHintingMode
+    = hintingModesCFFHash[engine->cffHintingEngineDefault];
+  currentTTInterpreterVersion
+    = hintingModesTrueTypeHash[engine->ttInterpreterVersionDefault];
+
+  hintingCheckBox->setChecked(true);
+
+  antiAliasingComboBoxx->setCurrentIndex(AntiAliasing_Normal);
+  lcdFilterComboBox->setCurrentIndex(LCDFilter_Light);
+
+  horizontalHintingCheckBox->setChecked(true);
+  verticalHintingCheckBox->setChecked(true);
+  blueZoneHintingCheckBox->setChecked(true);
+
+  showBitmapCheckBox->setChecked(true);
+  showOutlinesCheckBox->setChecked(true);
+
+  gammaSlider->setValue(18); // 1.8
+  sizeDoubleSpinBox->setValue(20);
+  dpiSpinBox->setValue(96);
+  zoomSpinBox->setValue(20);
+
+  checkHinting();
+  checkHintingMode();
+  checkAutoHinting();
+  checkAntiAliasing();
+  checkLcdFilter();
+  checkShowPoints();
+  checkUnits();
+  checkCurrentFontIndex();
+  checkCurrentFaceIndex();
+  checkCurrentNamedInstanceIndex();
+  adjustGlyphIndex(0);
+  zoom();
+}
+
+
+void
+MainGUI::readSettings()
+{
+  QSettings settings;
+//  QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
+//  QSize size = settings.value("size", QSize(400, 400)).toSize();
+//  resize(size);
+//  move(pos);
+}
+
+
+void
+MainGUI::writeSettings()
+{
+  QSettings settings;
+//  settings.setValue("pos", pos());
+//  settings.setValue("size", size());
+}
+
+
+// end of maingui.cpp
diff --git a/src/ftinspect.h b/src/ftinspect/maingui.hpp
similarity index 50%
rename from src/ftinspect.h
rename to src/ftinspect/maingui.hpp
index 7d6f86b..8ad6c30 100644
--- a/src/ftinspect.h
+++ b/src/ftinspect/maingui.hpp
@@ -1,38 +1,26 @@
-// ftinspect.h
+// maingui.hpp
 
-#ifndef FTINSPECT_H_
-#define FTINSPECT_H_
+// Copyright (C) 2016-2017 by Werner Lemberg.
 
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_CACHE_H
-#include FT_CFF_DRIVER_H
-#include FT_LCD_FILTER_H
-#include FT_MODULE_H
-#include FT_OUTLINE_H
-#include FT_TRUETYPE_DRIVER_H
 
-// internal FreeType header files; only available in the source code bundle
-#include FT_INTERNAL_DRIVER_H
-#include FT_INTERNAL_OBJECTS_H
+#pragma once
+
+#include "engine/engine.hpp"
+#include "rendering/glyphbitmap.hpp"
+#include "rendering/glyphoutline.hpp"
+#include "rendering/glyphpointnumbers.hpp"
+#include "rendering/glyphpoints.hpp"
+#include "widgets/qcomboboxx.hpp"
+#include "widgets/qgraphicsviewx.hpp"
+#include "widgets/qpushbuttonx.hpp"
+#include "widgets/qspinboxx.hpp"
 
 #include <QAction>
-#include <QApplication>
-#include <QButtonGroup>
 #include <QCheckBox>
 #include <QCloseEvent>
-#include <QColor>
 #include <QComboBox>
-#include <QDateTime>
-#include <QDesktopWidget>
-#include <QDir>
 #include <QDoubleSpinBox>
-#include <QFileDialog>
-#include <QFileInfo>
 #include <QFileSystemWatcher>
-#include <QGraphicsItem>
-#include <QGraphicsScene>
-#include <QGraphicsView>
 #include <QGridLayout>
 #include <QHash>
 #include <QHBoxLayout>
@@ -42,283 +30,20 @@
 #include <QMap>
 #include <QMenu>
 #include <QMenuBar>
-#include <QMessageBox>
-#include <QPainterPath>
 #include <QPen>
 #include <QPushButton>
 #include <QScrollBar>
-#include <QSettings>
 #include <QSignalMapper>
-#include <QSizePolicy>
 #include <QSlider>
 #include <QSpinBox>
-#include <QStandardItem>
-#include <QStandardItemModel>
 #include <QStatusBar>
 #include <QTabWidget>
 #include <QTimer>
-#include <QTransform>
 #include <QVariant>
-#include <QVector2D>
 #include <QVBoxLayout>
-#include <QWidget>
-
-
-class MainGUI;
-
-
-// This structure is used to map the (font, face, instance) index triplet to
-// abstract IDs (generated by a running number stored in MainGUI's
-// `faceCounter' member).
-//
-// Qt's `QMap' class needs an implementation of the `<' operator.
-
-struct FaceID
-{
-  int fontIndex;
-  int faceIndex;
-  int namedInstanceIndex;
-
-  FaceID();
-  FaceID(int, int, int);
-  bool operator<(const FaceID& other) const;
-};
-
-
-// FreeType specific data.
-
-class Engine
-{
-public:
-  Engine(MainGUI*);
-  ~Engine();
-
-  const QString& currentFamilyName();
-  const QString& currentStyleName();
-  QString glyphName(int);
-  int numberOfFaces(int);
-  int numberOfNamedInstances(int, int);
-  int loadFont(int, int, int); // returns number of glyphs
-  FT_Outline* loadOutline(int);
-  void removeFont(int);
-  void setCFFHintingMode(int);
-  void setTTInterpreterVersion(int);
-  void update();
-
-  friend class MainGUI;
-  friend FT_Error faceRequester(FTC_FaceID,
-                                FT_Library,
-                                FT_Pointer,
-                                FT_Face*);
-
-  // XXX cover all available modules
-  enum FontType
-  {
-    FontType_CFF,
-    FontType_TrueType,
-    FontType_Other
-  };
-
-private:
-  MainGUI* gui;
-
-  int faceCounter; // a running number used to initialize `faceIDMap'
-  QMap<FaceID, int> faceIDMap;
-
-  QString curFamilyName;
-  QString curStyleName;
-
-  FT_Library library;
-  FTC_Manager cacheManager;
-  FTC_ImageCache imageCache;
-  FTC_SBitCache sbitsCache;
-
-  FTC_ScalerRec scaler;
-  FT_Size ftSize;
 
-  int cffHintingEngineDefault;
-  int cffHintingEngineOther;
-
-  int ttInterpreterVersionDefault;
-  int ttInterpreterVersionOther;
-  int ttInterpreterVersionOther1;
-
-  int fontType;
-
-  int haveWarping;
-
-  double pointSize;
-  double pixelSize;
-  unsigned int dpi;
-
-  bool doHinting;
-  bool doAutoHinting;
-  bool doHorizontalHinting;
-  bool doVerticalHinting;
-  bool doBlueZoneHinting;
-  bool showSegments;
-  bool doWarping;
-
-  double gamma;
-
-  unsigned long loadFlags;
-};
-
-
-class Grid
-: public QGraphicsItem
-{
-public:
-  Grid(const QPen&,
-       const QPen&);
-  QRectF boundingRect() const;
-  void paint(QPainter*,
-             const QStyleOptionGraphicsItem*,
-             QWidget*);
-
-private:
-  QPen gridPen;
-  QPen axisPen;
-};
-
-
-class GlyphOutline
-: public QGraphicsItem
-{
-public:
-  GlyphOutline(const QPen&,
-               FT_Outline*);
-  QRectF boundingRect() const;
-  void paint(QPainter*,
-             const QStyleOptionGraphicsItem*,
-             QWidget*);
-
-private:
-  QPen outlinePen;
-  FT_Outline* outline;
-  QRectF bRect;
-};
-
-
-class GlyphPoints
-: public QGraphicsItem
-{
-public:
-  GlyphPoints(const QPen&,
-              const QPen&,
-              FT_Outline*);
-  QRectF boundingRect() const;
-  void paint(QPainter*,
-             const QStyleOptionGraphicsItem*,
-             QWidget*);
-
-private:
-  QPen onPen;
-  QPen offPen;
-  FT_Outline* outline;
-  QRectF bRect;
-};
-
-
-class GlyphPointNumbers
-: public QGraphicsItem
-{
-public:
-  GlyphPointNumbers(const QPen&,
-                    const QPen&,
-                    FT_Outline*);
-  QRectF boundingRect() const;
-  void paint(QPainter*,
-             const QStyleOptionGraphicsItem*,
-             QWidget*);
-
-private:
-  QPen onPen;
-  QPen offPen;
-  FT_Outline* outline;
-  QRectF bRect;
-};
-
-
-class GlyphBitmap
-: public QGraphicsItem
-{
-public:
-  GlyphBitmap(FT_Outline*,
-              FT_Library,
-              FT_Pixel_Mode,
-              const QVector<QRgb>&,
-              const QVector<QRgb>&);
-  ~GlyphBitmap();
-  QRectF boundingRect() const;
-  void paint(QPainter*,
-             const QStyleOptionGraphicsItem*,
-             QWidget*);
-
-private:
-  FT_Outline transformed;
-  FT_Library library;
-  unsigned char pixelMode;
-  const QVector<QRgb>& monoColorTable;
-  const QVector<QRgb>& grayColorTable;
-  QRectF bRect;
-};
-
-
-// we want to anchor the view at the bottom left corner
-// while the windows gets resized
-class QGraphicsViewx
-: public QGraphicsView
-{
-  Q_OBJECT
-
-public:
-  QGraphicsViewx();
-
-protected:
-  void resizeEvent(QResizeEvent*);
-  void scrollContentsBy(int, int);
-
-private:
-  QPointF lastBottomLeftPoint;
-  bool lastBottomLeftPointInitialized;
-};
-
-
-// we want to have our own `stepBy' function for the zoom spin box
-class QSpinBoxx
-: public QSpinBox
-{
-  Q_OBJECT
-
-public:
-  void stepBy(int);
-  int valueFromText(const QString&) const;
-};
-
-
-// we want to grey out items in a combo box;
-// since Qt doesn't provide a function for this we derive a class
-class QComboBoxx
-: public QComboBox
-{
-  Q_OBJECT
-
-public:
-  void setItemEnabled(int, bool);
-};
-
-
-// we want buttons that are horizontally as small as possible
-class QPushButtonx
-: public QPushButton
-{
-  Q_OBJECT
-
-public:
-  QPushButtonx(const QString&, QWidget* = 0);
-  virtual ~QPushButtonx(){}
-};
+#include <ft2build.h>
+#include FT_LCD_FILTER_H
 
 
 class MainGUI
@@ -374,8 +99,8 @@ private:
   QStringList fontList;
   int currentFontIndex;
 
-  int currentNumberOfFaces;
-  int currentFaceIndex;
+  long currentNumberOfFaces;
+  long currentFaceIndex;
 
   int currentNumberOfNamedInstances;
   int currentNamedInstanceIndex;
@@ -529,7 +254,7 @@ private:
     HintingMode_TrueType_v38,
     HintingMode_TrueType_v40,
     HintingMode_CFF_FreeType,
-    HintingMode_CFF_Adobe,
+    HintingMode_CFF_Adobe
   };
   enum LCDFilter
   {
@@ -557,7 +282,4 @@ private:
 };
 
 
-#endif // FTINSPECT_H_
-
-
-// end of ftinspect.h
+// end of maingui.hpp
diff --git a/src/ftinspect/rendering/glyphbitmap.cpp 
b/src/ftinspect/rendering/glyphbitmap.cpp
new file mode 100644
index 0000000..599de1d
--- /dev/null
+++ b/src/ftinspect/rendering/glyphbitmap.cpp
@@ -0,0 +1,128 @@
+// glyphbitmap.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "glyphbitmap.hpp"
+
+#include <cmath>
+#include <QPainter>
+#include <QStyleOptionGraphicsItem>
+
+
+GlyphBitmap::GlyphBitmap(FT_Outline* outline,
+                         FT_Library lib,
+                         FT_Pixel_Mode pxlMode,
+                         const QVector<QRgb>& monoColorTbl,
+                         const QVector<QRgb>& grayColorTbl)
+: library(lib),
+  pixelMode(pxlMode),
+  monoColorTable(monoColorTbl),
+  grayColorTable(grayColorTbl)
+{
+  // make a copy of the outline since we are going to manipulate it
+  FT_Outline_New(library,
+                 static_cast<unsigned int>(outline->n_points),
+                 outline->n_contours,
+                 &transformed);
+  FT_Outline_Copy(outline, &transformed);
+
+  FT_BBox cbox;
+  FT_Outline_Get_CBox(outline, &cbox);
+
+  cbox.xMin &= ~63;
+  cbox.yMin &= ~63;
+  cbox.xMax = (cbox.xMax + 63) & ~63;
+  cbox.yMax = (cbox.yMax + 63) & ~63;
+
+  // we shift the outline to the origin for rendering later on
+  FT_Outline_Translate(&transformed, -cbox.xMin, -cbox.yMin);
+
+  bRect.setCoords(cbox.xMin / 64, -cbox.yMax / 64,
+                  cbox.xMax / 64, -cbox.yMin / 64);
+}
+
+
+GlyphBitmap::~GlyphBitmap()
+{
+  FT_Outline_Done(library, &transformed);
+}
+
+QRectF
+GlyphBitmap::boundingRect() const
+{
+  return bRect;
+}
+
+
+void
+GlyphBitmap::paint(QPainter* painter,
+                   const QStyleOptionGraphicsItem* option,
+                   QWidget*)
+{
+  FT_Bitmap bitmap;
+
+  int height = static_cast<int>(ceil(bRect.height()));
+  int width = static_cast<int>(ceil(bRect.width()));
+  QImage::Format format = QImage::Format_Indexed8;
+
+  // XXX cover LCD and color
+  if (pixelMode == FT_PIXEL_MODE_MONO)
+    format = QImage::Format_Mono;
+
+  QImage image(QSize(width, height), format);
+
+  if (pixelMode == FT_PIXEL_MODE_MONO)
+    image.setColorTable(monoColorTable);
+  else
+    image.setColorTable(grayColorTable);
+
+  image.fill(0);
+
+  bitmap.rows = static_cast<unsigned int>(height);
+  bitmap.width = static_cast<unsigned int>(width);
+  bitmap.buffer = image.bits();
+  bitmap.pitch = image.bytesPerLine();
+  bitmap.pixel_mode = pixelMode;
+
+  FT_Error error = FT_Outline_Get_Bitmap(library,
+                                         &transformed,
+                                         &bitmap);
+  if (error)
+  {
+    // XXX error handling
+    return;
+  }
+
+  // `drawImage' doesn't work as expected:
+  // the larger the zoom, the more the pixel rectangle positions
+  // deviate from the grid lines
+#if 0
+  painter->drawImage(QPoint(bRect.left(), bRect.top()),
+                     image.convertToFormat(
+                       QImage::Format_ARGB32_Premultiplied));
+#else
+  const qreal lod = option->levelOfDetailFromTransform(
+                              painter->worldTransform());
+
+  painter->setPen(Qt::NoPen);
+
+  for (int x = 0; x < image.width(); x++)
+    for (int y = 0; y < image.height(); y++)
+    {
+      // be careful not to lose the alpha channel
+      QRgb p = image.pixel(x, y);
+      painter->fillRect(QRectF(x + bRect.left() - 1 / lod / 2,
+                               y + bRect.top() - 1 / lod / 2,
+                               1 + 1 / lod,
+                               1 + 1 / lod),
+                        QColor(qRed(p),
+                               qGreen(p),
+                               qBlue(p),
+                               qAlpha(p)));
+    }
+#endif
+}
+
+
+// end of glyphbitmap.cpp
diff --git a/src/ftinspect/rendering/glyphbitmap.hpp 
b/src/ftinspect/rendering/glyphbitmap.hpp
new file mode 100644
index 0000000..7fd9a1a
--- /dev/null
+++ b/src/ftinspect/rendering/glyphbitmap.hpp
@@ -0,0 +1,41 @@
+// glyphbitmap.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPen>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+
+
+class GlyphBitmap
+: public QGraphicsItem
+{
+public:
+  GlyphBitmap(FT_Outline* outline,
+              FT_Library library,
+              FT_Pixel_Mode pixelMode,
+              const QVector<QRgb>& monoColorTable,
+              const QVector<QRgb>& grayColorTable);
+  ~GlyphBitmap();
+  QRectF boundingRect() const;
+  void paint(QPainter* painter,
+             const QStyleOptionGraphicsItem* option,
+             QWidget* widget);
+
+private:
+  FT_Outline transformed;
+  FT_Library library;
+  unsigned char pixelMode;
+  const QVector<QRgb>& monoColorTable;
+  const QVector<QRgb>& grayColorTable;
+  QRectF bRect;
+};
+
+
+// end of glyphbitmap.hpp
diff --git a/src/ftinspect/rendering/glyphoutline.cpp 
b/src/ftinspect/rendering/glyphoutline.cpp
new file mode 100644
index 0000000..bf47c26
--- /dev/null
+++ b/src/ftinspect/rendering/glyphoutline.cpp
@@ -0,0 +1,129 @@
+// glyphoutline.cpp
+
+// Copyright (C) 2016=2017 by Werner Lemberg.
+
+
+#include "glyphoutline.hpp"
+
+#include <QPainter>
+
+
+extern "C" {
+
+// vertical font coordinates are bottom-up,
+// while Qt uses top-down
+
+static int
+moveTo(const FT_Vector* to,
+       void* user)
+{
+  QPainterPath* path = static_cast<QPainterPath*>(user);
+
+  path->moveTo(qreal(to->x) / 64,
+               -qreal(to->y) / 64);
+
+  return 0;
+}
+
+
+static int
+lineTo(const FT_Vector* to,
+       void* user)
+{
+  QPainterPath* path = static_cast<QPainterPath*>(user);
+
+  path->lineTo(qreal(to->x) / 64,
+               -qreal(to->y) / 64);
+
+  return 0;
+}
+
+
+static int
+conicTo(const FT_Vector* control,
+        const FT_Vector* to,
+        void* user)
+{
+  QPainterPath* path = static_cast<QPainterPath*>(user);
+
+  path->quadTo(qreal(control->x) / 64,
+               -qreal(control->y) / 64,
+               qreal(to->x) / 64,
+               -qreal(to->y) / 64);
+
+  return 0;
+}
+
+
+static int
+cubicTo(const FT_Vector* control1,
+        const FT_Vector* control2,
+        const FT_Vector* to,
+        void* user)
+{
+  QPainterPath* path = static_cast<QPainterPath*>(user);
+
+  path->cubicTo(qreal(control1->x) / 64,
+                -qreal(control1->y) / 64,
+                qreal(control2->x) / 64,
+                -qreal(control2->y) / 64,
+                qreal(to->x) / 64,
+                -qreal(to->y) / 64);
+
+  return 0;
+}
+
+
+static FT_Outline_Funcs outlineFuncs =
+{
+  moveTo,
+  lineTo,
+  conicTo,
+  cubicTo,
+  0, // no shift
+  0  // no delta
+};
+
+} // extern "C"
+
+
+GlyphOutline::GlyphOutline(const QPen& outlineP,
+                           FT_Outline* outln)
+: outlinePen(outlineP),
+  outline(outln)
+{
+  FT_BBox cbox;
+
+  qreal halfPenWidth = outlinePen.widthF();
+
+  FT_Outline_Get_CBox(outline, &cbox);
+
+  bRect.setCoords(qreal(cbox.xMin) / 64 - halfPenWidth,
+                  -qreal(cbox.yMax) / 64 - halfPenWidth,
+                  qreal(cbox.xMax) / 64 + halfPenWidth,
+                  -qreal(cbox.yMin) / 64 + halfPenWidth);
+}
+
+
+QRectF
+GlyphOutline::boundingRect() const
+{
+  return bRect;
+}
+
+
+void
+GlyphOutline::paint(QPainter* painter,
+                    const QStyleOptionGraphicsItem*,
+                    QWidget*)
+{
+  painter->setPen(outlinePen);
+
+  QPainterPath path;
+  FT_Outline_Decompose(outline, &outlineFuncs, &path);
+
+  painter->drawPath(path);
+}
+
+
+// end of glyphoutline.cpp
diff --git a/src/ftinspect/rendering/glyphoutline.hpp 
b/src/ftinspect/rendering/glyphoutline.hpp
new file mode 100644
index 0000000..ec136cf
--- /dev/null
+++ b/src/ftinspect/rendering/glyphoutline.hpp
@@ -0,0 +1,34 @@
+// glyphoutline.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPen>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+
+
+class GlyphOutline
+: public QGraphicsItem
+{
+public:
+  GlyphOutline(const QPen& pen,
+               FT_Outline* outline);
+  QRectF boundingRect() const;
+  void paint(QPainter* painter,
+             const QStyleOptionGraphicsItem* option,
+             QWidget* widget);
+
+private:
+  QPen outlinePen;
+  FT_Outline* outline;
+  QRectF bRect;
+};
+
+
+// end of glyphoutline.hpp
diff --git a/src/ftinspect/rendering/glyphpointnumbers.cpp 
b/src/ftinspect/rendering/glyphpointnumbers.cpp
new file mode 100644
index 0000000..c17c2eb
--- /dev/null
+++ b/src/ftinspect/rendering/glyphpointnumbers.cpp
@@ -0,0 +1,206 @@
+// glyphpointnumbers.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "glyphpointnumbers.hpp"
+
+#include <QPainter>
+#include <QStyleOptionGraphicsItem>
+#include <QVector2D>
+
+
+GlyphPointNumbers::GlyphPointNumbers(const QPen& onP,
+                                     const QPen& offP,
+                                     FT_Outline* outln)
+: onPen(onP),
+  offPen(offP),
+  outline(outln)
+{
+  FT_BBox cbox;
+
+  FT_Outline_Get_CBox(outline, &cbox);
+
+  // XXX fix bRect size
+  bRect.setCoords(qreal(cbox.xMin) / 64,
+                  -qreal(cbox.yMax) / 64,
+                  qreal(cbox.xMax) / 64,
+                  -qreal(cbox.yMin) / 64);
+}
+
+
+QRectF
+GlyphPointNumbers::boundingRect() const
+{
+  return bRect;
+}
+
+
+void
+GlyphPointNumbers::paint(QPainter* painter,
+                         const QStyleOptionGraphicsItem* option,
+                         QWidget*)
+{
+  const qreal lod = option->levelOfDetailFromTransform(
+                              painter->worldTransform());
+
+  // don't draw point numbers if magnification is too small
+  if (lod >= 10)
+  {
+    QFont font = painter->font();
+
+    // the following doesn't work correctly with scaling;
+    // it seems that Qt doesn't allow arbitrarily small font sizes
+    // that get magnified later on
+#if 0
+    // we want the same text size regardless of the scaling
+    font.setPointSizeF(font.pointSizeF() / lod);
+    painter->setFont(font);
+#else
+    font.setPointSizeF(font.pointSizeF() * 3 / 4);
+    painter->setFont(font);
+
+    QBrush onBrush(onPen.color());
+    QBrush offBrush(offPen.color());
+
+    painter->scale(1 / lod, 1 / lod);
+#endif
+
+    FT_Vector* points = outline->points;
+    FT_Short* contours = outline->contours;
+    char* tags = outline->tags;
+
+    QVector2D octants[8] = { QVector2D(1, 0),
+                             QVector2D(0.707f, -0.707f),
+                             QVector2D(0, -1),
+                             QVector2D(-0.707f, -0.707f),
+                             QVector2D(-1, 0),
+                             QVector2D(-0.707f, 0.707f),
+                             QVector2D(0, 1),
+                             QVector2D(0.707f, 0.707f) };
+
+
+    short ptIdx = 0;
+    for (int contIdx = 0; contIdx < outline->n_contours; contIdx++ )
+    {
+      for (;;)
+      {
+        short prevIdx, nextIdx;
+
+        // find previous and next point in outline
+        if (contIdx == 0)
+        {
+          if (contours[contIdx] == 0)
+          {
+            prevIdx = 0;
+            nextIdx = 0;
+          }
+          else
+          {
+            prevIdx = ptIdx > 0 ? ptIdx - 1
+                                : contours[contIdx];
+            nextIdx = ptIdx < contours[contIdx] ? ptIdx + 1
+                                                : 0;
+          }
+        }
+        else
+        {
+          prevIdx = ptIdx > (contours[contIdx - 1] + 1) ? ptIdx - 1
+                                                        : contours[contIdx];
+          nextIdx = ptIdx < contours[contIdx] ? ptIdx + 1
+                                              : contours[contIdx - 1] + 1;
+        }
+
+        // get vectors to previous and next point and normalize them;
+        QVector2D in(static_cast<float>(points[prevIdx].x
+                                        - points[ptIdx].x) / 64,
+                     -static_cast<float>(points[prevIdx].y
+                                         - points[ptIdx].y) / 64);
+        QVector2D out(static_cast<float>(points[nextIdx].x
+                                         - points[ptIdx].x) / 64,
+                      -static_cast<float>(points[nextIdx].y
+                                          - points[ptIdx].y) / 64);
+
+        in = in.normalized();
+        out = out.normalized();
+
+        QVector2D middle = in + out;
+        // check whether vector is very small, using a threshold of 1/8px
+        if (qAbs(middle.x()) < 1.0f / 8
+            && qAbs(middle.y()) < 1.0f / 8)
+        {
+          // in case of vectors in almost exactly opposite directions,
+          // use a vector orthogonal to them
+          middle.setX(out.y());
+          middle.setY(-out.x());
+
+          if (qAbs(middle.x()) < 1.0f / 8
+              && qAbs(middle.y()) < 1.0f / 8)
+          {
+            // use direction based on point index for the offset
+            // if we still don't have a good value
+            middle = octants[ptIdx % 8];
+          }
+        }
+
+        // normalize `middle' vector (which is never zero),
+        // then multiply by 8 to get some distance between
+        // the point and the number
+        middle = middle.normalized() * 8;
+
+        // we now position the point number in the opposite
+        // direction of the `middle' vector,
+        QString number = QString::number(ptIdx);
+
+#if 0
+        // this fails, see comment above
+        int size = 10000;
+        qreal x = qreal(points[ptIdx].x) / 64 - middle.x() / lod;
+        qreal y = -qreal(points[ptIdx].y) / 64 - middle.y() / lod;
+        QPointF corner(x, y);
+        int flags = middle.x() > 0 ? Qt::AlignRight
+                                   : Qt::AlignLeft;
+        if (flags == Qt::AlignRight)
+          corner.rx() -= size;
+        QRectF posRect(corner, QSizeF(size, size));
+
+        if (tags[ptIdx] & FT_CURVE_TAG_ON)
+          painter->setPen(onPen);
+        else
+          painter->setPen(offPen);
+
+        painter->drawText(posRect, flags, number);
+#else
+        // convert text string to a path object
+        QPainterPath path;
+        path.addText(QPointF(0, 0), font, number);
+        QRectF ctrlPtRect = path.controlPointRect();
+
+        qreal x = static_cast<qreal>(points[ptIdx].x) / 64 * lod
+                  - static_cast<qreal>(middle.x());
+        qreal y = -static_cast<qreal>(points[ptIdx].y) / 64 * lod
+                  - static_cast<qreal>(middle.y());
+
+        qreal heuristicOffset = 2;
+        if (middle.x() > 0)
+          path.translate(x - ctrlPtRect.width() - heuristicOffset,
+                         y + ctrlPtRect.height() / 2);
+        else
+          path.translate(x,
+                         y + ctrlPtRect.height() / 2);
+
+        painter->fillPath(path,
+                          tags[ptIdx] & FT_CURVE_TAG_ON ? onBrush
+                                                        : offBrush);
+#endif
+
+        ptIdx++;
+        if (ptIdx > contours[contIdx])
+          break;
+      }
+    }
+  }
+}
+
+
+// end of glyphpointnumbers.cpp
diff --git a/src/ftinspect/rendering/glyphpointnumbers.hpp 
b/src/ftinspect/rendering/glyphpointnumbers.hpp
new file mode 100644
index 0000000..d392684
--- /dev/null
+++ b/src/ftinspect/rendering/glyphpointnumbers.hpp
@@ -0,0 +1,36 @@
+// glyphpointnumbers.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPen>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+
+
+class GlyphPointNumbers
+: public QGraphicsItem
+{
+public:
+  GlyphPointNumbers(const QPen& onPen,
+                    const QPen& offPen,
+                    FT_Outline* outline);
+  QRectF boundingRect() const;
+  void paint(QPainter* painter,
+             const QStyleOptionGraphicsItem* option,
+             QWidget* widget);
+
+private:
+  QPen onPen;
+  QPen offPen;
+  FT_Outline* outline;
+  QRectF bRect;
+};
+
+
+// end of glyphpointnumbers.hpp
diff --git a/src/ftinspect/rendering/glyphpoints.cpp 
b/src/ftinspect/rendering/glyphpoints.cpp
new file mode 100644
index 0000000..7e5fcd2
--- /dev/null
+++ b/src/ftinspect/rendering/glyphpoints.cpp
@@ -0,0 +1,108 @@
+// glyphpoints.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "glyphpoints.hpp"
+
+#include <QPainter>
+#include <QStyleOptionGraphicsItem>
+
+
+GlyphPoints::GlyphPoints(const QPen& onP,
+                         const QPen& offP,
+                         FT_Outline* outln)
+: onPen(onP),
+  offPen(offP),
+  outline(outln)
+{
+  FT_BBox cbox;
+
+  qreal halfPenWidth = qMax(onPen.widthF(), offPen.widthF()) / 2;
+
+  FT_Outline_Get_CBox(outline, &cbox);
+
+  bRect.setCoords(qreal(cbox.xMin) / 64 - halfPenWidth,
+                  -qreal(cbox.yMax) / 64 - halfPenWidth,
+                  qreal(cbox.xMax) / 64 + halfPenWidth,
+                  -qreal(cbox.yMin) / 64 + halfPenWidth);
+}
+
+
+QRectF
+GlyphPoints::boundingRect() const
+{
+  return bRect;
+}
+
+
+void
+GlyphPoints::paint(QPainter* painter,
+                   const QStyleOptionGraphicsItem* option,
+                   QWidget*)
+{
+  const qreal lod = option->levelOfDetailFromTransform(
+                              painter->worldTransform());
+
+  // don't draw points if magnification is too small
+  if (lod >= 5)
+  {
+    // we want the same dot size regardless of the scaling;
+    // for good optical results, the pen widths should be uneven integers
+
+    // interestingly, using `drawPoint' doesn't work as expected:
+    // the larger the zoom, the more horizontally stretched the dot appears
+#if 0
+    qreal origOnPenWidth = onPen.widthF();
+    qreal origOffPenWidth = offPen.widthF();
+
+    onPen.setWidthF(origOnPenWidth / lod);
+    offPen.setWidthF(origOffPenWidth / lod);
+
+    for (int i = 0; i < outline->n_points; i++)
+    {
+      if (outline->tags[i] & FT_CURVE_TAG_ON)
+        painter->setPen(onPen);
+      else
+        painter->setPen(offPen);
+
+      painter->drawPoint(QPointF(qreal(outline->points[i].x) / 64,
+                                 -qreal(outline->points[i].y) / 64));
+    }
+
+    onPen.setWidthF(origOnPenWidth);
+    offPen.setWidthF(origOffPenWidth);
+#else
+    QBrush onBrush(onPen.color());
+    QBrush offBrush(offPen.color());
+
+    painter->setPen(Qt::NoPen);
+
+    qreal onRadius = onPen.widthF() / lod;
+    qreal offRadius = offPen.widthF() / lod;
+
+    for (int i = 0; i < outline->n_points; i++)
+    {
+      if (outline->tags[i] & FT_CURVE_TAG_ON)
+      {
+        painter->setBrush(onBrush);
+        painter->drawEllipse(QPointF(qreal(outline->points[i].x) / 64,
+                                     -qreal(outline->points[i].y) / 64),
+                             onRadius,
+                             onRadius);
+      }
+      else
+      {
+        painter->setBrush(offBrush);
+        painter->drawEllipse(QPointF(qreal(outline->points[i].x) / 64,
+                                     -qreal(outline->points[i].y) / 64),
+                             offRadius,
+                             offRadius);
+      }
+    }
+#endif
+  }
+}
+
+
+// end of glyphpoints.cpp
diff --git a/src/ftinspect/rendering/glyphpoints.hpp 
b/src/ftinspect/rendering/glyphpoints.hpp
new file mode 100644
index 0000000..fc0cc8a
--- /dev/null
+++ b/src/ftinspect/rendering/glyphpoints.hpp
@@ -0,0 +1,36 @@
+// glyphpoints.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPen>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+
+
+class GlyphPoints
+: public QGraphicsItem
+{
+public:
+  GlyphPoints(const QPen& onPen,
+              const QPen& offPen,
+              FT_Outline* outline);
+  QRectF boundingRect() const;
+  void paint(QPainter* painter,
+             const QStyleOptionGraphicsItem* option,
+             QWidget* widget);
+
+private:
+  QPen onPen;
+  QPen offPen;
+  FT_Outline* outline;
+  QRectF bRect;
+};
+
+
+// end of glyphpoints.hpp
diff --git a/src/ftinspect/rendering/grid.cpp b/src/ftinspect/rendering/grid.cpp
new file mode 100644
index 0000000..adabf26
--- /dev/null
+++ b/src/ftinspect/rendering/grid.cpp
@@ -0,0 +1,93 @@
+// grid.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "grid.hpp"
+
+#include <QPainter>
+#include <QStyleOptionGraphicsItem>
+
+
+Grid::Grid(const QPen& gridP,
+           const QPen& axisP)
+: gridPen(gridP),
+  axisPen(axisP)
+{
+ // empty
+}
+
+
+QRectF
+Grid::boundingRect() const
+{
+  // XXX fix size
+
+  // no need to take care of pen width
+  return QRectF(-100, -100,
+                200, 200);
+}
+
+
+// XXX call this in a `myQDraphicsView::drawBackground' derived method
+//     to always fill the complete viewport
+
+void
+Grid::paint(QPainter* painter,
+            const QStyleOptionGraphicsItem* option,
+            QWidget*)
+{
+  const qreal lod = option->levelOfDetailFromTransform(
+                              painter->worldTransform());
+
+  painter->setPen(gridPen);
+
+  // don't mark pixel center with a cross if magnification is too small
+  if (lod > 20)
+  {
+    int halfLength = 1;
+
+    // cf. QSpinBoxx
+    if (lod > 640)
+      halfLength = 6;
+    else if (lod > 320)
+      halfLength = 5;
+    else if (lod > 160)
+      halfLength = 4;
+    else if (lod > 80)
+      halfLength = 3;
+    else if (lod > 40)
+      halfLength = 2;
+
+    for (qreal x = -100; x < 100; x++)
+      for (qreal y = -100; y < 100; y++)
+      {
+        painter->drawLine(QLineF(x + 0.5, y + 0.5 - halfLength / lod,
+                                 x + 0.5, y + 0.5 + halfLength / lod));
+        painter->drawLine(QLineF(x + 0.5 - halfLength / lod, y + 0.5,
+                                 x + 0.5 + halfLength / lod, y + 0.5));
+      }
+  }
+
+  // don't draw grid if magnification is too small
+  if (lod >= 5)
+  {
+    // XXX fix size
+    for (int x = -100; x <= 100; x++)
+      painter->drawLine(x, -100,
+                        x, 100);
+    for (int y = -100; y <= 100; y++)
+      painter->drawLine(-100, y,
+                        100, y);
+  }
+
+  painter->setPen(axisPen);
+
+  painter->drawLine(0, -100,
+                    0, 100);
+  painter->drawLine(-100, 0,
+                    100, 0);
+}
+
+
+// end of grid.cpp
diff --git a/src/ftinspect/rendering/grid.hpp b/src/ftinspect/rendering/grid.hpp
new file mode 100644
index 0000000..f138911
--- /dev/null
+++ b/src/ftinspect/rendering/grid.hpp
@@ -0,0 +1,29 @@
+// grid.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPen>
+
+
+class Grid
+: public QGraphicsItem
+{
+public:
+  Grid(const QPen& gridPen,
+       const QPen& axisPen);
+  QRectF boundingRect() const;
+  void paint(QPainter* painter,
+             const QStyleOptionGraphicsItem* option,
+             QWidget* widget);
+
+private:
+  QPen gridPen;
+  QPen axisPen;
+};
+
+
+// end of grid.hpp
diff --git a/src/ftinspect/widgets/qcomboboxx.cpp 
b/src/ftinspect/widgets/qcomboboxx.cpp
new file mode 100644
index 0000000..de49141
--- /dev/null
+++ b/src/ftinspect/widgets/qcomboboxx.cpp
@@ -0,0 +1,37 @@
+// qcomboboxx.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "qcomboboxx.hpp"
+
+#include <QStandardItemModel>
+
+
+void
+QComboBoxx::setItemEnabled(int index,
+                           bool enable)
+{
+  const QStandardItemModel* itemModel =
+    qobject_cast<const QStandardItemModel*>(model());
+  QStandardItem* item = itemModel->item(index);
+
+  if (enable)
+  {
+    item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+    item->setData(QVariant(),
+                  Qt::TextColorRole);
+  }
+  else
+  {
+    item->setFlags(item->flags()
+                   & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
+    // clear item data in order to use default color;
+    // this visually greys out the item
+    item->setData(palette().color(QPalette::Disabled, QPalette::Text),
+                  Qt::TextColorRole);
+  }
+}
+
+
+// end of qcomboboxx.cpp
diff --git a/src/ftinspect/widgets/qcomboboxx.hpp 
b/src/ftinspect/widgets/qcomboboxx.hpp
new file mode 100644
index 0000000..e4f9794
--- /dev/null
+++ b/src/ftinspect/widgets/qcomboboxx.hpp
@@ -0,0 +1,24 @@
+// qcomboboxx.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QComboBox>
+
+
+// we want to grey out items in a combo box;
+// since Qt doesn't provide a function for this we derive a class
+class QComboBoxx
+: public QComboBox
+{
+  Q_OBJECT
+
+public:
+  void setItemEnabled(int index,
+                      bool enable);
+};
+
+
+// end of qcomboboxx.hpp
diff --git a/src/ftinspect/widgets/qgraphicsviewx.cpp 
b/src/ftinspect/widgets/qgraphicsviewx.cpp
new file mode 100644
index 0000000..9fdd230
--- /dev/null
+++ b/src/ftinspect/widgets/qgraphicsviewx.cpp
@@ -0,0 +1,49 @@
+// qgraphicsviewx.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "qgraphicsviewx.hpp"
+
+#include <QScrollBar>
+
+
+QGraphicsViewx::QGraphicsViewx()
+: lastBottomLeftPointInitialized(false)
+{
+  // empty
+}
+
+
+void
+QGraphicsViewx::scrollContentsBy(int dx,
+                                 int dy)
+{
+  QGraphicsView::scrollContentsBy(dx, dy);
+  lastBottomLeftPoint = viewport()->rect().bottomLeft();
+}
+
+
+void
+QGraphicsViewx::resizeEvent(QResizeEvent* event)
+{
+  QGraphicsView::resizeEvent(event);
+
+  // XXX I don't know how to properly initialize this value,
+  //     thus the hack with the boolean
+  if (!lastBottomLeftPointInitialized)
+  {
+    lastBottomLeftPoint = viewport()->rect().bottomLeft();
+    lastBottomLeftPointInitialized = true;
+  }
+
+  QPointF currentBottomLeftPoint = viewport()->rect().bottomLeft();
+  int verticalPosition = verticalScrollBar()->value();
+  verticalScrollBar()->setValue(static_cast<int>(
+                                  verticalPosition
+                                  - (currentBottomLeftPoint.y()
+                                     - lastBottomLeftPoint.y())));
+}
+
+
+// end of qgraphicsviewx.cpp
diff --git a/src/ftinspect/widgets/qgraphicsviewx.hpp 
b/src/ftinspect/widgets/qgraphicsviewx.hpp
new file mode 100644
index 0000000..62927ce
--- /dev/null
+++ b/src/ftinspect/widgets/qgraphicsviewx.hpp
@@ -0,0 +1,32 @@
+// qgraphicsviewx.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QGraphicsView>
+
+
+// we want to anchor the view at the bottom left corner
+// while the windows gets resized
+class QGraphicsViewx
+: public QGraphicsView
+{
+  Q_OBJECT
+
+public:
+  QGraphicsViewx();
+
+protected:
+  void resizeEvent(QResizeEvent* event);
+  void scrollContentsBy(int dx,
+                        int dy);
+
+private:
+  QPointF lastBottomLeftPoint;
+  bool lastBottomLeftPointInitialized;
+};
+
+
+// end of qgraphicsviewx.hpp
diff --git a/src/ftinspect/widgets/qpushbuttonx.cpp 
b/src/ftinspect/widgets/qpushbuttonx.cpp
new file mode 100644
index 0000000..b834114
--- /dev/null
+++ b/src/ftinspect/widgets/qpushbuttonx.cpp
@@ -0,0 +1,33 @@
+// qpushbuttonx.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "qpushbuttonx.hpp"
+
+#include <QStyleOptionButton>
+
+
+// code derived from Qt 4.8.7, function `QPushButton::sizeHint',
+// file `src/gui/widgets/qpushbutton.cpp'
+
+QPushButtonx::QPushButtonx(const QString &text,
+                           QWidget *parent)
+: QPushButton(text, parent)
+{
+  QStyleOptionButton opt;
+  opt.initFrom(this);
+  QString s(this->text());
+  QFontMetrics fm = fontMetrics();
+  QSize sz = fm.size(Qt::TextShowMnemonic, s);
+  opt.rect.setSize(sz);
+
+  sz = style()->sizeFromContents(QStyle::CT_PushButton,
+                                 &opt,
+                                 sz,
+                                 this);
+  setFixedWidth(sz.width());
+}
+
+
+// end of qpushbuttonx.cpp
diff --git a/src/ftinspect/widgets/qpushbuttonx.hpp 
b/src/ftinspect/widgets/qpushbuttonx.hpp
new file mode 100644
index 0000000..c841dd6
--- /dev/null
+++ b/src/ftinspect/widgets/qpushbuttonx.hpp
@@ -0,0 +1,24 @@
+// qpushbuttonx.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QPushButton>
+
+
+// we want buttons that are horizontally as small as possible
+class QPushButtonx
+: public QPushButton
+{
+  Q_OBJECT
+
+public:
+  QPushButtonx(const QString& text,
+               QWidget* = 0);
+  virtual ~QPushButtonx(){}
+};
+
+
+// end of qpushbuttonx.hpp
diff --git a/src/ftinspect/widgets/qspinboxx.cpp 
b/src/ftinspect/widgets/qspinboxx.cpp
new file mode 100644
index 0000000..9734eed
--- /dev/null
+++ b/src/ftinspect/widgets/qspinboxx.cpp
@@ -0,0 +1,85 @@
+// qspinboxx.cpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#include "qspinboxx.hpp"
+
+
+// we want to mark the center of a pixel square with a single dot or a small
+// cross; starting with a certain magnification we thus only use even values
+// so that we can do that symmetrically
+
+int
+QSpinBoxx::valueFromText(const QString& text) const
+{
+  int val = QSpinBox::valueFromText(text);
+
+  if (val > 640)
+    val = val - (val % 64);
+  else if (val > 320)
+    val = val - (val % 32);
+  else if (val > 160)
+    val = val - (val % 16);
+  else if (val > 80)
+    val = val - (val % 8);
+  else if (val > 40)
+    val = val - (val % 4);
+  else if (val > 20)
+    val = val - (val % 2);
+
+  return val;
+}
+
+
+void
+QSpinBoxx::stepBy(int steps)
+{
+  int val = value();
+
+  if (steps > 0)
+  {
+    for (int i = 0; i < steps; i++)
+    {
+      if (val >= 640)
+        val = val + 64;
+      else if (val >= 320)
+        val = val + 32;
+      else if (val >= 160)
+        val = val + 16;
+      else if (val >= 80)
+        val = val + 8;
+      else if (val >= 40)
+        val = val + 4;
+      else if (val >= 20)
+        val = val + 2;
+      else
+        val++;
+    }
+  }
+  else if (steps < 0)
+  {
+    for (int i = 0; i < -steps; i++)
+    {
+      if (val > 640)
+        val = val - 64;
+      else if (val > 320)
+        val = val - 32;
+      else if (val > 160)
+        val = val - 16;
+      else if (val > 80)
+        val = val - 8;
+      else if (val > 40)
+        val = val - 4;
+      else if (val > 20)
+        val = val - 2;
+      else
+        val--;
+    }
+  }
+
+  setValue(val);
+}
+
+
+// end of qspinboxx.cpp
diff --git a/src/ftinspect/widgets/qspinboxx.hpp 
b/src/ftinspect/widgets/qspinboxx.hpp
new file mode 100644
index 0000000..f89d88f
--- /dev/null
+++ b/src/ftinspect/widgets/qspinboxx.hpp
@@ -0,0 +1,24 @@
+// qspinboxx.hpp
+
+// Copyright (C) 2016-2017 by Werner Lemberg.
+
+
+#pragma once
+
+#include <QSpinBox>
+#include <QString>
+
+
+// we want to have our own `stepBy' function for the zoom spin box
+class QSpinBoxx
+: public QSpinBox
+{
+  Q_OBJECT
+
+public:
+  void stepBy(int val);
+  int valueFromText(const QString& text) const;
+};
+
+
+// qspinboxx.hpp



reply via email to

[Prev in Thread] Current Thread [Next in Thread]