FreeTypeFaces text drawing with hinting!


#1

FreeType rendering with hinting has arrived to Juce! Here’s what you need to do:

  • Set up the include paths and link your project with FreeType (http://www.freetype.org) on Mac and Windows
  • Add these two files to your project
  • Turn your TrueType or OpenType font file into a variable in your application using the Juce BinaryBuilder
  • Add some magic lines to your custom LookAndFeel

This is MIT-licensed and therefore compatible with both the GPL and the commercial license for Juce!

FreeTypeFaces.h

// Copyright (C) 2008-2011 by One Guy Group, Inc., All rights reserved worldwide.
/*******************************************************************************

FreeType CustomTypeFace with hinting for Juce
By Vincent Falco

Juce:
http://www.rawmaterialsoftware.com

--------------------------------------------------------------------------------

License: MIT License (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2011 by Vincent Falco

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*******************************************************************************/

#ifndef FREETYPEFACES_H
#define FREETYPEFACES_H

#include "juce.h"

// This singleton uses FreeType to open font files and
// extract glyph outlines, with the option of using hinting
// at a customizable range of sizes.
class FreeTypeFaces
{
public:
  // Add a font file that has been loaded into memory.
  // If appendStyleToFaceName is false, the style name is
  // not added to the family name to create the typeface name.
  // For example "Helvetica Neue LT Com 65 Medium" becomes
  // "Helvetica Neue LT Com" if appendStyleToFaceName is false.
  //
  // This is useful when you have two styled versions of a face,
  // for example "Helvetica Neue LT Com 65 Medium" and
  // "Helvetica Neue LT Com 75 Bold" and you want to treat them
  // as a single font with an optional bold style. Adding both
  // of these faces with appendStyleToFaceName=false will allow them
  // to work as a single typeface named "Helvetica Neue LT Com"
  // while also respecting the bold flag in the Font object.
  //
  // On the other hand if you have many different weights of the
  // same font you might want appendStyleToFaceName=true so that
  // you can precisely identify which weight you want, for advanced
  // typographists.
  //
  // If useFreeTypeRendering is true and the font gets hinted,
  // it will use FreeType to rasterize the glyph outlines, taking
  // advantage of even more hinting information (if present).
  static void addFaceFromMemory (float minHintedHeight,
                                 float maxHintedHeight,
                                 bool useFreeTypeRendering,
                                 const void* faceFileData,
                                 int faceFileBytes,
                                 bool appendStyleToFaceName = false);

  // This will created a hinted or unhinted Typeface depending
  // on the size of the font, and the range of heights given when
  // the face was added. If the font does not match any faces
  // previoulsy added with addFaceFromMemory(), this function returns 0.
  static Typeface::Ptr createTypefaceForFont (const Font& font);
};

/* To use a hinted font for your entire application you will need to
   implement your own custom LookAndFeel, and override the getTypefaceForFont()
   function. This is an example of what mine looks like:

class CustomLookAndFeel
{
public:
  CustomLookAndFeel()
  {
    // Add the TrueType font "Helvetica Neue LT Com 65 Medium" and
    // activate hinting when the font height is between 7 and 12 inclusive.
    // The font data was generated by running the Juce BinaryBuilder
    // program on the actual TrueType font file (.ttf)
    FreeTypeFaces::getInstance()->addFaceFromMemory(
      7.f, 12.f,
      binaries::helveticaneueltcommd_ttf,
      binaries::helveticaneueltcommd_ttfSize);
  }

  // This function will replace the default sans serif font used
  // throughout the application to use our hinted FreeType face.
  const Typeface::Ptr CustomLookAndFeel::getTypefaceForFont (const Font &font)
  {
    Typeface::Ptr tf;

    String faceName (font.getTypefaceName());

    if (faceName == Font::getDefaultSansSerifFontName())
    {
      // use our hinted font
      Font f(font);
      f.setTypefaceName ("Helvetica Neue LT Com");
      tf = FreeTypeFaces::createTypefaceForFont (f);
    }

    if (!tf)
      tf = LookAndFeel::getTypefaceForFont (font);

    return tf;
  }
};
*/
#endif

FreeTypeFaces.cpp

// Copyright (C) 2008-2011 by One Guy Group, Inc., All rights reserved worldwide.
/*******************************************************************************

FreeType CustomTypeFace for Juce
By Vincent Falco

Juce:
http://www.rawmaterialsoftware

--------------------------------------------------------------------------------

License: MIT License (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2011 by Vincent Falco

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*******************************************************************************/

#include "FreeTypeFaces.h"

#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftgasp.h>

//------------------------------------------------------------------------------
/*
 * Obviously the first thing people are going to want is to be able to get
 * hinted output from the currently installed system fonts instead of a
 * font embedded in the application's static variables but that's not something
 * I need. But here's a pointer to locating the font files on windows:
 *
 */
//http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=4190&zep=XFont.cpp&rzp=%2fKB%2fGDI%2fxfont%2f%2fxfont_demo.zip
//

// This allows you to use FreeType to render the bitmaps instead of Juce
// and in theory, could produce better results but in my tests, its exactly
// the same output. Which is a good thing :-)
//
// In order to enable this you will need a patch to Juce
//#define TYPEFACE_BITMAP_RENDERING

// anonymous namespace
namespace {

// avoided the juce::Singleton because of order of initialization issues
class FreeTypeLibrary : public ReferenceCountedObject
{
private:
  static FreeTypeLibrary* s_instance;
  FT_Library m_ft;

public:
  typedef ReferenceCountedObjectPtr<FreeTypeLibrary> Ptr;

  FreeTypeLibrary ()
  {
    FT_Error result;

    result = FT_Init_FreeType (&m_ft);
  }

  ~FreeTypeLibrary ()
  {
    s_instance = 0;
    FT_Done_FreeType (m_ft);
  }

  FT_Library getLibrary ()
  {
    return m_ft;
  }

  static Ptr getInstance()
  {
    if (s_instance)
    {
      return s_instance;
    }
    else
    {
      s_instance = new FreeTypeLibrary;
      return s_instance;
    }
  }
};

FreeTypeLibrary* FreeTypeLibrary::s_instance = 0;

//------------------------------------------------------------------------------

// A non-hinted CustomTypeface that uses FreeType to open the font file
// and extract the outlines. It is considered a match for any font whose
// name and style also match, and for which the font height lies outside
// the range specified by minHintedHeight and maxHintedHeight.
class FreeTypeFace : public CustomTypeface
{
private:
  FreeTypeLibrary::Ptr m_ft;
  float m_minHintedHeight;
  float m_maxHintedHeight;
  float m_scale;
  float m_kerningScale;
  int m_kerningMode;

protected:
  FT_Face m_face;
  int m_loadFlags;

public:
  FreeTypeFace (float minHintedHeight,
                float maxHintedHeight,
                const void* fileData,
                int fileBytes)
    : m_face(0)
    , m_minHintedHeight (minHintedHeight)
    , m_maxHintedHeight (maxHintedHeight)
    , m_kerningScale (1)
  {
    m_ft = FreeTypeLibrary::getInstance();

    openMemoryFace (fileData, fileBytes);
  }

  ~FreeTypeFace()
  { 
    closeFace();
  }

  bool isSuitableForFont (const Font& font) const
  {
    // sometimes Juce, during initialization and in the
    // menubar and documentwindow drawing code, tries to
    // produce fonts with zero heights. We never want to hint these
    if (font.getHeight()<1)
      return true; // never hint these

    // don't use this for heights within
    // the range for which hinting is desired.
    return (font.getHeight() > m_maxHintedHeight ||
            font.getHeight() < m_minHintedHeight);
  }

  bool isHinted () const
  {
    return false;
  }

protected:
  FreeTypeFace ()
    : m_face (0)
    , m_kerningScale (1)
  {
    m_ft = FreeTypeLibrary::getInstance();
  }

  bool openMemoryFace (const void* fileData, int fileBytes )
  {
    bool wasOpened = false;

    closeFace();

    FT_Error error;

    error = FT_New_Memory_Face (m_ft->getLibrary(),
                                (FT_Byte*)fileData,
                                fileBytes,
                                0,
                                &m_face);
    if (!error)
    {
      error = FT_Select_Charmap (m_face, FT_ENCODING_UNICODE);
      if (error)
        error = FT_Set_Charmap (m_face, m_face->charmaps[0]);

      if (!error)
      {
        prepareFace ();

        updateCharacteristics();

        wasOpened = true;
      }
    }

    return wasOpened;
  }

  void closeFace ()
  {
    clear();
    if (m_face)
    {
      /*FT_Error error =*/ FT_Done_Face (m_face);
      m_face = 0;
    }
  }

  void setParameters (double scale,
                      int loadFlags,
                      double kerningScale,
                      FT_UInt kerningMode)
  {
    m_scale = scale;
    m_loadFlags = loadFlags;
    m_kerningScale = kerningScale;
    m_kerningMode = kerningMode;
  }

  // this is from juce_linux_Fonts.cpp and I'm not sure if Jules
  // wants it MIT-licensed which is why I provided the other version.
  /*
  bool convertOutlineToPathJuce (Path& destShape, const FT_Outline* outline)
  {
    const short* const contours = outline->contours;
    const char* const tags = outline->tags;
    FT_Vector* const points = outline->points;

    for (int c = 0; c < outline->n_contours; c++)
    {
      const int startPoint = (c == 0) ? 0 : contours [c - 1] + 1;
      const int endPoint = contours[c];

      for (int p = startPoint; p <= endPoint; p++)
      {
        const float x = points[p].x;
        const float y = points[p].y;

        if (p == startPoint)
        {
          if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
          {
            float x2 = points [endPoint].x;
            float y2 = points [endPoint].y;

            if (FT_CURVE_TAG (tags[endPoint]) != FT_Curve_Tag_On)
            {
              x2 = (x + x2) * 0.5f;
              y2 = (y + y2) * 0.5f;
            }

            destShape.startNewSubPath (x2, y2);
          }
          else
          {
            destShape.startNewSubPath (x, y);
          }
        }

        if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_On)
        {
          if (p != startPoint)
            destShape.lineTo (x, y);
        }
        else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
        {
          const int nextIndex = (p == endPoint) ? startPoint : p + 1;
          float x2 = points [nextIndex].x;
          float y2 = points [nextIndex].y;

          if (FT_CURVE_TAG (tags [nextIndex]) == FT_Curve_Tag_Conic)
          {
            x2 = (x + x2) * 0.5f;
            y2 = (y + y2) * 0.5f;
          }
          else
          {
            ++p;
          }

          destShape.quadraticTo (x, y, x2, y2);
        }
        else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Cubic)
        {
          if (p >= endPoint)
            return false;

          const int next1 = p + 1;
          const int next2 = (p == (endPoint - 1)) ? startPoint : p + 2;

          const float x2 = points [next1].x;
          const float y2 = points [next1].y;
          const float x3 = points [next2].x;
          const float y3 = points [next2].y;

          if (FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic
            || FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On)
            return false;

          destShape.cubicTo (x, y, x2, y2, x3, y3);
          p += 2;
        }
      }

      destShape.closeSubPath();
    }

    return true;
  }
  */

  bool convertOutlineToPath (Path& destShape, const FT_Outline* outline)
  {   
    typedef float value_type;

    FT_Vector v_last;
    FT_Vector v_control;
    FT_Vector v_start;
    value_type x1, y1, x2, y2, x3, y3;

    FT_Vector* point;
    FT_Vector* limit;
    char* tags;

    int n;         // index of contour in outline
    int first;     // index of first point in contour
    char tag;      // current point's state

    first = 0;

    for(n = 0; n < outline->n_contours; n++)
    {
      int last; // index of last point in contour

      last  = outline->contours[n];
      limit = outline->points + last;

      v_start = outline->points[first];
      v_last  = outline->points[last];

      v_control = v_start;

      point = outline->points + first;
      tags  = outline->tags  + first;
      tag   = FT_CURVE_TAG(tags[0]);

      // A contour cannot start with a cubic control point!
      if(tag == FT_CURVE_TAG_CUBIC)
        return false;

      // check first point to determine origin
      if( tag == FT_CURVE_TAG_CONIC)
      {
        // first point is conic control.  Yes, this happens.
        if(FT_CURVE_TAG(outline->tags[last]) == FT_CURVE_TAG_ON)
        {
          // start at last point if it is on the curve
          v_start = v_last;
          limit--;
        }
        else
        {
          // if both first and last points are conic,
          // start at their middle and record its position
          // for closure
          v_start.x = (v_start.x + v_last.x) / 2;
          v_start.y = (v_start.y + v_last.y) / 2;

          v_last = v_start;
        }
        point--;
        tags--;
      }

      x1 = v_start.x; y1 = v_start.y;
      destShape.startNewSubPath (x1, y1);

      while(point < limit)
      {
        point++;
        tags++;

        tag = FT_CURVE_TAG(tags[0]);
        switch(tag)
        {
        case FT_CURVE_TAG_ON:  // emit a single lineTo
          {
            x1 = point->x; y1 = point->y;
            destShape.lineTo(x1, y1);
            continue;
          }

        case FT_CURVE_TAG_CONIC:  // consume conic arcs
          {
            v_control.x = point->x;
            v_control.y = point->y;

Do_Conic:
            if(point < limit)
            {
              FT_Vector vec;
              FT_Vector v_middle;

              point++;
              tags++;
              tag = FT_CURVE_TAG(tags[0]);

              vec.x = point->x; vec.y = point->y;

              if(tag == FT_CURVE_TAG_ON)
              {
                x1 = v_control.x; y1 = v_control.y;
                x2 = vec.x; y2 = vec.y;
                destShape.quadraticTo (x1, y1, x2, y2);
                continue;
              }

              if(tag != FT_CURVE_TAG_CONIC)
                return false;

              v_middle.x = (v_control.x + vec.x) / 2;
              v_middle.y = (v_control.y + vec.y) / 2;

              x1 = v_control.x; y1 = v_control.y;
              x2 = v_middle.x; y2 = v_middle.y;
              destShape.quadraticTo (x1, y1, x2, y2);
              v_control = vec;
              goto Do_Conic;
            }

            x1 = v_control.x; y1 = v_control.y;
            x2 = v_start.x; y2 = v_start.y;
            destShape.quadraticTo (x1, y1, x2, y2);
            goto Close;
          }

        default:  // FT_CURVE_TAG_CUBIC
          {
            FT_Vector vec1, vec2;

            if(point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC)
              return false;

            vec1.x = point[0].x;  vec1.y = point[0].y;
            vec2.x = point[1].x;  vec2.y = point[1].y;

            point += 2;
            tags  += 2;

            if(point <= limit)
            {
              FT_Vector vec;

              vec.x = point->x; vec.y = point->y;

              x1 = vec1.x; y1 = vec1.y;
              x2 = vec2.x; y2 = vec2.y;
              x3 = vec.x; y3 = vec.y;
              destShape.cubicTo(x1, y1, x2, y2, x3, y3);
              continue;
            }

            x1 = vec1.x; y1 = vec1.y;
            x2 = vec2.x; y2 = vec2.y;
            x3 = v_start.x; y3 = v_start.y;
            destShape.cubicTo (x1, y1, x2, y2, x3, y3);
            goto Close;
          }
        }
      }

      destShape.closeSubPath();

Close:
      first = last + 1; 
    }

    return true;
  }

  virtual void prepareFace ()
  {
    // calculate outline scale factor
    float scale = 1;
    // convert from font units to Juce normalized
    scale *= 1./m_face->units_per_EM;
    // fudge since Juce produces smaller paths than FreeType
    // when it uses the Win32 API to extract the curves (?)
    float boxHeight = float(m_face->bbox.yMax - m_face->bbox.yMin);
    float fudge = m_face->units_per_EM / boxHeight;
    // this small adjustment produces output identical to Juce under win32
    fudge *= 1.0059625f;

    setParameters (scale * fudge,
                   FT_LOAD_NO_BITMAP | FT_LOAD_NO_SCALE,
                   fudge/ float(m_face->ascender - m_face->descender),
                   FT_KERNING_UNSCALED);
  }

private:
  void updateCharacteristics()
  {
    String name = m_face->family_name;
    float ascent = float(m_face->bbox.yMax) / (m_face->bbox.yMax-m_face->bbox.yMin);
    bool isBold = (m_face->style_flags & FT_STYLE_FLAG_BOLD) != 0;
    bool isItalic = (m_face->style_flags & FT_STYLE_FLAG_ITALIC) != 0;
    
    // ??? what do I put here?
    juce_wchar defaultChar = 0;//255;

    setCharacteristics (name, ascent, isBold, isItalic, defaultChar);

    addKerningPairs ();
  }

  void addKerningPairs()
  {
    FT_Error error = 0;

    FT_UInt leftGlyphIndex;
    FT_ULong leftCharCode = FT_Get_First_Char (m_face, &leftGlyphIndex);
    while (!error && leftGlyphIndex)
    {
      addKerningPairsForGlyph (leftGlyphIndex, leftCharCode);

      leftCharCode = FT_Get_Next_Char (m_face, leftCharCode, &leftGlyphIndex);
    }
  }

  FT_Error addKerningPairsForGlyph (FT_UInt leftGlyphIndex, FT_ULong leftCharCode)
  {
    FT_Error error = 0;

    if ((m_face->face_flags & FT_FACE_FLAG_KERNING) != 0)
    {
      FT_UInt rightGlyphIndex;
      FT_ULong rightCharCode = FT_Get_First_Char (m_face, &rightGlyphIndex);
      while (!error && rightGlyphIndex)
      {
        FT_Vector kerning;
        error = FT_Get_Kerning (m_face,
                                leftGlyphIndex,
                                rightGlyphIndex,
                                m_kerningMode,
                                &kerning);
        if (!error)
        {
          if (kerning.x != 0)
          {
            float extraAmount = m_kerningScale * kerning.x;
            addKerningPair (leftCharCode, rightCharCode, extraAmount);
          }

          rightCharCode = FT_Get_Next_Char (m_face, rightCharCode, &rightGlyphIndex);
        }
      }
    }

    return error;
  }

private:
  bool loadGlyphIfPossible (juce_wchar characterNeeded)
  {
    bool wasLoaded = false;
    
    FT_Error error = 0;
    
    FT_UInt glyph_index = FT_Get_Char_Index (m_face, characterNeeded);
    if (glyph_index == 0)
      error = -1;

    if (!error)
      error = FT_Load_Glyph (m_face, glyph_index, m_loadFlags);

    if (!error)
    {
      Path path;
      wasLoaded = convertOutlineToPath (path, &m_face->glyph->outline);
      if (wasLoaded)
      {
#if 0
        // This stuff doesn't work yet

        // I'm assuming that we never get called to load a char twice.
        jassert (m_face->glyph->generic.data == 0);
        m_face->glyph->generic.data = (void*)1;

        // Delay loading of kerning pairs since there could be a
        // lot of code points in a multilanguage-aware face
        addKerningPairsForGlyph (glyph_index, characterNeeded);
#endif

        float advance = m_face->glyph->metrics.horiAdvance;
        // convert to juce normalized units
        path.applyTransform (AffineTransform::scale(m_scale, -m_scale));
        advance *= m_scale;
        addGlyph (characterNeeded, path, advance);
      }
    }

    return wasLoaded;
  }
};

//------------------------------------------------------------------------------

class FreeTypeHintedFace : public FreeTypeFace
{
private:
  // this is used as a substitute PositionedGlyph
  // so we can let FreeType do the rendering. This
  // only works for software rendering.
#ifdef TYPEFACE_BITMAP_RENDERING
  class PositionedGlyphImage : public PositionedGlyph
  {
  private:
    ReferenceCountedObjectPtr<FreeTypeHintedFace> m_hf;

  public:
    PositionedGlyphImage (FreeTypeHintedFace* hintedFace,
                          float x_,
                          float y_,
                          float w_,
                          const Font& font_,
                          juce_wchar character_,
                          int glyph_)
      : PositionedGlyph (x_, y_, w_, font_, character_, glyph_)
      , m_hf (hintedFace)
    {
    }

    void draw (const Graphics& g) const
    {
      if (!m_hf->drawGlyph (g, x, y, w, font, character, glyph))
        PositionedGlyph::draw (g);
    }
  };
#endif

private:
  float m_fontHeight; // the original font height that Juce sees
  bool m_useFreeTypeRendering; // true to use FreeType to render the outlines

public:
  FreeTypeHintedFace (float fontHeight,
                      bool useFreeTypeRendering,
                      const void* fileData,
                      int fileBytes)
    : m_fontHeight (fontHeight)
    , m_useFreeTypeRendering (useFreeTypeRendering)
  {
    openMemoryFace (fileData, fileBytes);
  }

  bool useTypefaceFor (const Font& font) const
  {
    return m_fontHeight == font.getHeight();
  }

  bool isSuitableForFont (const Font& font) const
  {
    return font.getHeight () == m_fontHeight;
  }

  bool isHinted () const
  {
    return true;
  }

#ifdef TYPEFACE_BITMAP_RENDERING
  PositionedGlyph* createPositionedGlyph (float x, float y, float w, const Font& font, juce_wchar character, int glyph)
  {
    if (m_useFreeTypeRendering)
      return new PositionedGlyphImage (this, x, y, w, font, character, glyph);
    else
      return Typeface::createPositionedGlyph (x, y, w, font, character, glyph);
  }
#endif

protected:
  void prepareFace ()
  {
    // calculate a fudged font scale to make things match Juce
    float fontScale;
    fontScale = 0.854f; // empirical
    float adjustedHeight = m_fontHeight * fontScale;

    // calculate outline scale factor
    float scale = 1.f;
    // convert 26p6 screen pixels to fractional screen pixels
    scale *= 1. / 64;
    // convert to normalized based on created height
    scale *= 1. / adjustedHeight;
    // account for the discrepancy in the juce height and the created height
    scale *= adjustedHeight / m_fontHeight;

    //FT_Int gasp = FT_Get_Gasp (m_face,
    //                           FT_UInt(m_fontHeight * m_face->units_per_EM));

    int loadFlags = 0;
    loadFlags |= FT_LOAD_NO_BITMAP;
    //if (gasp == FT_GASP_NO_TABLE)
    loadFlags |= FT_LOAD_FORCE_AUTOHINT;
    //loadFlags |= FT_LOAD_TARGET_LIGHT;

    setParameters(scale, loadFlags,
                  scale, FT_KERNING_DEFAULT);

    FT_Set_Char_Size (m_face, 0, FT_F26Dot6 (adjustedHeight * 64.f + 0.5f), 0, 0);
  }
  
  class FreeTypeImage : public Image::SharedImage
  {
  public:
    FreeTypeImage (Image::PixelFormat format_,
                 int width_,
                 int height_,
                 int lineStride_,
                 uint8* imageData_)
    : SharedImage (format_, width_, height_)
    {
      pixelStride = 1;
      lineStride = lineStride_;
      imageData = imageData_;
    }

    ~FreeTypeImage()
    {
    }

    LowLevelGraphicsContext* createLowLevelContext()
    {
      // this image is read-only
      jassertfalse;
      return 0;
    }

    SharedImage* clone()
    {
      SharedImage* dup = new FreeTypeImage (format, width, height, lineStride, imageData);
      //dup->userData = userData; /* unfortunate */
      return dup;
    }

    Image::ImageType getType() const
    {
      return Image::SoftwareImage;
    }
  };

  //----------------------------------------------------------------------------

  bool drawGlyph (const Graphics& g,
                  float x,
                  float y,
                  float w,
                  const Font& font,
                  juce_wchar character,
                  int glyph)
  {
    bool couldDraw = false;
    LowLevelGraphicsContext& lg = *g.getInternalContext();

    FT_Error error=0;
    
    if (lg.isVectorDevice())
      error = -1;

    if (!error)
      error = FT_Load_Char (m_face, character, m_loadFlags);

    if (!error)
      error = FT_Render_Glyph (m_face->glyph, FT_RENDER_MODE_NORMAL);

    if (!error)
    {
      int w = m_face->glyph->bitmap.width;
      int h = m_face->glyph->bitmap.rows;
      if (w>0 && h>0)
      {
#if 0
        uint8* srcRow = m_face->glyph->bitmap.buffer;

        Image im (Image::SingleChannel, w, h, false);
        {
          Image::BitmapData dest (im, 0, 0, w, h, true);

          for (int y = 0; y<h; y++ )
          {
            uint8* destRow = dest.getLinePointer (y);
            memcpy (destRow, srcRow, w);
            srcRow += m_face->glyph->bitmap.pitch;
          }
        }

#else
        FreeTypeImage* fim = new FreeTypeImage(Image::SingleChannel, w, h,
          m_face->glyph->bitmap.pitch, m_face->glyph->bitmap.buffer);
        Image im (fim);
#endif

#if 0
        lg.drawImage (im, AffineTransform::translation(
          int(x + m_face->glyph->bitmap_left+.5f),
          int(y - m_face->glyph->bitmap_top+.5f)), false);
#else
        g.drawImage (im,
          int(x + m_face->glyph->bitmap_left+.5f),
          int(y - m_face->glyph->bitmap_top+.5f),
          w, h, 0, 0, w, h, true);
#endif
      }
    }

    if (!error)
      couldDraw = true;

    return couldDraw;
  }
};

//------------------------------------------------------------------------------

class FreeTypeFacesImplementation : public DeletedAtShutdown
{
private:
  struct MemoryFace
  {
    int flags;
    String faceName;
    String actualName; // family + style
    float minHintedHeight;
    float maxHintedHeight;
    bool useFreeTypeRendering;
    const void* faceFileData;
    int faceFileBytes;
  };

  FreeTypeLibrary::Ptr m_ft;
  Array<MemoryFace> m_faces;

public:
  FreeTypeFacesImplementation()
    : m_ft (FreeTypeLibrary::getInstance())
  {
  }

  FreeTypeFacesImplementation::~FreeTypeFacesImplementation()
  {
    clearSingletonInstance();
  }

  void addFaceFromMemory (float minHintedHeight,
                          float maxHintedHeight,
                          bool useFreeTypeRendering,
                          const void* faceFileData,
                          int faceFileBytes,
                          bool appendStyleToFaceName)
  {
    FT_Error error;
    
    FT_Face face;
    error = FT_New_Memory_Face (m_ft->getLibrary(),
                                (FT_Byte*)faceFileData,
                                faceFileBytes,
                                0,
                                &face);
    if (!error)
    {
      if (face->face_flags & FT_FACE_FLAG_SCALABLE)
      {
        MemoryFace mf;

        mf.flags = 0;
        if (face->style_flags & FT_STYLE_FLAG_BOLD)
          mf.flags |= Font::bold;
        if (face->style_flags & FT_STYLE_FLAG_ITALIC)
          mf.flags |= Font::italic;

        mf.actualName = face->family_name;
        mf.actualName << ' ' << face->style_name;

        if (appendStyleToFaceName)
          mf.faceName = mf.actualName;
        else
          mf.faceName = face->family_name;

        mf.minHintedHeight = minHintedHeight;
        mf.maxHintedHeight = maxHintedHeight;
        mf.useFreeTypeRendering = useFreeTypeRendering;
        mf.faceFileData = faceFileData;
        mf.faceFileBytes = faceFileBytes;

        String s ("Added FreeType family '");
        s << face->family_name
          << "' with style '"
          << face->style_name
          << "'";

        if (mf.flags == (Font::bold | Font::italic))
          s << " as bold+italic";
        else if (mf.flags == Font::bold)
          s << " as bold";
        else if (mf.flags == Font::italic)
          s << " as italic";
        else if (mf.flags != 0)
          s << " with flags=" << String(mf.flags);

        Logger::outputDebugString (s);

        FT_Done_Face (face);

        m_faces.add (mf);
      }
    }
  }

  Typeface::Ptr createTypefaceForFont (const Font& font)
  {
    Typeface::Ptr typeFace = 0;

    for (int i=0; i<m_faces.size(); i++)
    {
      MemoryFace mf (m_faces[i]);
      if (mf.faceName == font.getTypefaceName() &&
          mf.flags == (font.getStyleFlags() & (Font::bold | Font::italic)))
      {
        bool useHinting = (font.getHeight() >= mf.minHintedHeight) &&
                          (font.getHeight() <= mf.maxHintedHeight);

        if (useHinting)
          typeFace = new FreeTypeHintedFace(
            font.getHeight(),
            mf.useFreeTypeRendering,
            mf.faceFileData,
            mf.faceFileBytes);
        else
          typeFace = new FreeTypeFace(
            mf.minHintedHeight,
            mf.maxHintedHeight,
            mf.faceFileData,
            mf.faceFileBytes);

        String s ("Created FreeType face '");
        s << mf.actualName << "'";
        if (useHinting)
          s << " at hinted size " << String(useHinting ? font.getHeight() : 0., 2);
        const int flags = font.getStyleFlags() & (Font::bold | Font::italic);
        if (flags)
        {
          if (flags == (Font::bold | Font::italic))
            s << " as bold+italic";
          else if (flags == Font::bold)
            s << " as bold";
          else if (flags == Font::italic)
            s << " as italic";
          else if (flags != 0)
            s << " with flags=" << String(flags);
        }

        Logger::outputDebugString (s);
        break;
      }
    }

    return typeFace;
  }

  juce_DeclareSingleton (FreeTypeFacesImplementation, false);
};

juce_ImplementSingleton (FreeTypeFacesImplementation)

}

//------------------------------------------------------------------------------

void FreeTypeFaces::addFaceFromMemory (float minHintedHeight,
                                       float maxHintedHeight,
                                       bool useFreeTypeRendering,
                                       const void* faceFileData,
                                       int faceFileBytes,
                                       bool appendStyleToFaceName)
{
  return FreeTypeFacesImplementation::getInstance()->addFaceFromMemory(
                                                      minHintedHeight,
                                                      maxHintedHeight,
                                                      useFreeTypeRendering,
                                                      faceFileData,
                                                      faceFileBytes,
                                                      appendStyleToFaceName);
}

Typeface::Ptr FreeTypeFaces::createTypefaceForFont (const Font& font)
{
  return FreeTypeFacesImplementation::getInstance()->createTypefaceForFont (font);
}

#2

Here’s a comparison:

[attachment=2]hinting_demo.gif[/attachment]

Rendering without hinting (not so bad after all)
[attachment=1]juce.png[/attachment]

FreeType hinting (small sizes appear better to some people)
[attachment=0]freetype.png[/attachment]


#3

Are these suitable for Chinese characters?


#4

Yes, but with the following warning:

If your font has glyphs for chinese code points then yes it will work but be aware that in Windows, Juce uses 16-bit characters which unfortunately means you cannot access any Unicode codepoints that require surrogate pairs. If/when Jules is ready to address this issue I can certainly help because its a non trivial change that will affect a lot of code (but the result will be worth it).

Keep in mind the Linux version of Juce uses FreeType, so if Linux is displaying your characters then so will my code.

Why don’t you try it and find out!


#5

Beautiful! Thank you Vinn. My project will benefit from this tremendously.


#6

Glad to hear it! I would love to see screenshots.


#7

Hmm, I can’t get it to compile. Complaints about using private members:

[code]f:\devstudio\projects\primitives\jaudioplugin\freetypefaces.cpp(636) : error C2248: ‘juce::PositionedGlyph::PositionedGlyph’ : cannot access private member declared in class 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(52020) : see declaration of 'juce::PositionedGlyph::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(51971) : see declaration of 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\jaudioplugin\freetypefaces.cpp(641) : error C2248: ‘juce::PositionedGlyph::x’ : cannot access private member declared in class 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(52015) : see declaration of 'juce::PositionedGlyph::x’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(51971) : see declaration of 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\jaudioplugin\freetypefaces.cpp(641) : error C2248: ‘juce::PositionedGlyph::y’ : cannot access private member declared in class 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(52015) : see declaration of 'juce::PositionedGlyph::y’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(51971) : see declaration of 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\jaudioplugin\freetypefaces.cpp(641) : error C2248: ‘juce::PositionedGlyph::w’ : cannot access private member declared in class 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(52015) : see declaration of 'juce::PositionedGlyph::w’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(51971) : see declaration of 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\jaudioplugin\freetypefaces.cpp(641) : error C2248: ‘juce::PositionedGlyph::font’ : cannot access private member declared in class 'juce::PositionedGlyph’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(52016) : see declaration of 'juce::PositionedGlyph::font’
f:\devstudio\projects\primitives\juce\1_52\juce_amalgamated.h(51971) : see declaration of ‘juce::PositionedGlyph’

[/code]
…and more.
I’m using 1.52. What are you using Vinn?


#8
  1. You have to use the latest tip since it requires some cooperation with the Juce Typeface class, which didn’t get added until after 1.52

  2. Did you define “TYPEFACE_BITMAP_RENDERING” ? Because that requires a patch to Juce. I can post it for you, but it is unsupported, probably won’t make it into the tip, and I think you should first try to get it working without TYPEFACE_BITMAP_RENDERING defined.


#9

[quote=“TheVinn”]

  1. You have to use the latest tip since it requires some cooperation with the Juce Typeface class, which didn’t get added until after 1.52[/quote]
    Ok, I’ll go fetch it.

[quote=“TheVinn”]
2) Did you define “TYPEFACE_BITMAP_RENDERING” ? Because that requires a patch to Juce. I can post it for you, but it is unsupported, probably won’t make it into the tip, and I think you should first try to get it working without TYPEFACE_BITMAP_RENDERING defined.[/quote]I did, but in 1.52 there doesn’t seem to be any reference to TYPEFACE_BITMAP_RENDERING so perhaps it’s a pre 1.53 thing. I’ll download the latest tip and see what happens.

Why is it called “tip” by the way…? Does it refer to the bleeding edge?


#10

TYPEFACE_BITMAP_RENDERING is my macro. Turning it on activates additional code that uses FreeType not only to extract hinted outlines for a given size, but also uses its built in rasterizer to generate a bitmap. It requires a patch to Juce, which will probably never be supported.

Tests show that Juce produces an identical result to FreeType when rendering anyway. However, letting FreeType render means that you can get bitmap fonts to work.


#11

Hey Vinn,
I really like your freetype approach, because IMHO the only weakness of juce is the blurry font rendering under certain circumstances.
I impleneted your code but the result is not exactly what I expected.

When I use a binary built font with your freetype code, the readability of a single glyph is much better, but the hinting is completely wrong (see “emphasize Drums” or “isolate Bassdrum” in the image below).

No matter which font I use, the result is a wrong hinted text.
Is the code you are using for your own projects the same code that you posted here or did you change anything in the meantime?
Did I miss anything?

Thanks.


#12

[quote=“friscokid”]When I use a binary built font with your freetype code, the readability of a single glyph is much better, but the hinting is completely wrong. No matter which font I use, the result is a wrong hinted text.
Is the code you are using for your own projects the same code that you posted here or did you change anything in the meantime?
Did I miss anything?[/quote]

Yes, there was a bug. I forgot exactly what it was but I will begin researching it right away.

What is happening, is not a problem with the hinting, it is because it is using the wrong outlines. It is using hinted outlines for a different size, and scaling it down to the requested size, instead of using a newly generated hinted font at the requested size.


#13

Yes, I found the fix. FreeTypeHintedFace was missing the function isSuitableForFont:

bool isSuitableForFont (const Font& font) const { return font.getHeight () == m_fontHeight; }

I have updated the original post to include this function in class FreeTypeHintedFace.

This should fix your problem, please let me know right away if you have any other issues and I will take care of it immediately!


#14

Awesome, that’s it!
Thanks a lot.


#15

Unfortunately I’m having issues with FreeTypeFaces.cpp:

Error 34 error C2065: 'pixelStride' : undeclared identifier xxx\freetypefaces.cpp 729 Error 35 error C2065: 'lineStride' : undeclared identifier xxx\freetypefaces.cpp 730 Error 36 error C2065: 'imageData' : undeclared identifier xxx\freetypefaces.cpp 731 Error 37 error C2259: '`anonymous-namespace'::FreeTypeHintedFace::FreeTypeImage' : cannot instantiate abstract class xxx\freetypefaces.cpp 747 Error 38 error C2065: 'lineStride' : undeclared identifier xxx\freetypefaces.cpp 747 Error 39 error C2065: 'imageData' : undeclared identifier xxx\freetypefaces.cpp 747 Error 40 error C2259: '`anonymous-namespace'::FreeTypeHintedFace::FreeTypeImage' : cannot instantiate abstract class xxx\freetypefaces.cpp 804

The undeclared identifiers are odd because those are public members of the base class Image::SharedImage.

I’d guess that error 2259 is because FreeTypeImage isn’t implementing initialiseBitmapData which is a pure virtual function? What I don’t understand is why I’d get these errors and others aren’t!

-Andrew

PS I’m using Juce 1.54.27 in VS2010/W7x64…


#16

I don’t want to spoil your party but unless you have an embedding license for the font you’re certainly not allowed to just add the font to your app and distribute it. Surely there must be a way to load a system font without embedding it into the app?


#17

Sure there’s a way. A platform specific way that requires extra code, and is completely outside the scope of what I wrote. In fact, there is a comment at the very top of the .cpp file that addresses your question:

/*
* Obviously the first thing people are going to want is to be able to get
* hinted output from the currently installed system fonts instead of a
* font embedded in the application's static variables but that's not something
* I need. But here's a pointer to locating the font files on windows:
*/
http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=4190&zep=XFont.cpp&rzp=%2fKB%2fGDI%2fxfont%2f%2fxfont_demo.zip

That url is http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=4190&zep=XFont.cpp&rzp=%2fKB%2fGDI%2fxfont%2f%2fxfont_demo.zip


#18

Have come back to this weeks later and am still stalled.

[quote=“Andrew J”]Unfortunately I’m having issues with FreeTypeFaces.cpp:

Error 34 error C2065: 'pixelStride' : undeclared identifier xxx\freetypefaces.cpp 729 Error 35 error C2065: 'lineStride' : undeclared identifier xxx\freetypefaces.cpp 730 Error 36 error C2065: 'imageData' : undeclared identifier xxx\freetypefaces.cpp 731 Error 37 error C2259: '`anonymous-namespace'::FreeTypeHintedFace::FreeTypeImage' : cannot instantiate abstract class xxx\freetypefaces.cpp 747 Error 38 error C2065: 'lineStride' : undeclared identifier xxx\freetypefaces.cpp 747 Error 39 error C2065: 'imageData' : undeclared identifier xxx\freetypefaces.cpp 747 Error 40 error C2259: '`anonymous-namespace'::FreeTypeHintedFace::FreeTypeImage' : cannot instantiate abstract class xxx\freetypefaces.cpp 804

The undeclared identifiers are odd because those are public members of the base class Image::BitmapData.

I’d guess that error 2259 is because FreeTypeImage isn’t implementing initialiseBitmapData which is a pure virtual function? What I don’t understand is why I’d get these errors and others aren’t!

-Andrew

PS I’m using Juce 1.54.27 in VS2010/W7x64…
[/quote]
Any ideas on this anyone? Not sure why others can compile and I can’t.


#19

Oh well, forget it for now - instead I’ll ask about implementing this after the Jucequake. Looks like Image::SharedImage is gone, so it looks like a bit of rework is required.

I guess the warning for SharedImage should have been heeded :wink:


#20

Image::SharedImage has actually been renamed, and had a few changes to its methods, but it shouldn’t be too hard to update to the new version.