// --------------------------------------------------------------------
// Rendering fonts onto the canvas
// Using some code from Xpdf, copyright 2001-2002 Glyph & Cog, LLC
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipefonts.h"
#include "ipefontpool.h"
#include "ipepdfparser.h"

#include <qdir.h>
#include <qstring.h>
#include <qbuffer.h>
#include <qmessagebox.h>
#include <qimage.h>

#include <ft2build.h>
#include FT_FREETYPE_H

extern bool getStdFontWidths(IpeString fontName,
			     const IpeString encoding[0x100],
			     int width[0x100]);

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

struct Engine {
  Engine();
  ~Engine();

  bool iOk;
  bool iReported;
  FT_Library iLib;
};

// Auto-constructed and destructed Freetype engine.
static Engine engine;

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

Engine::Engine()
{
  iOk = false;
  iReported = false;
  if (FT_Init_FreeType(&iLib))
    return;
  iOk = true;
}

Engine::~Engine()
{
  if (iOk)
    FT_Done_FreeType(iLib);
}

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

/*! \class IpeCanvasFonts
  \brief Provides the fonts used to render text.

*/

IpeCanvasFonts *IpeCanvasFonts::New(bool aa, const IpeFontPool *fontPool,
				    IpeFontsServices *services)
{
  if (!engine.iOk)
    return 0;
  if (!engine.iReported) {
    engine.iReported = true;
#if FREETYPE_MINOR >= 1
    int major, minor, patch;
    FT_Library_Version(engine.iLib, &major, &minor, &patch);
    ipeDebug("Using Freetype version %d.%d.%d", major, minor, patch);

    if (major != FREETYPE_MAJOR ||
	minor != FREETYPE_MINOR ||
	patch != FREETYPE_PATCH) {
      QMessageBox::critical
	(0, "Ipe",
	 QObject::tr("Ipe has been compiled using the header files of "
		     "Freetype version %1.%2.%3, but is linked with Freetype "
		     "library version %4.%5.%6.  No text display is possible.")
	 .arg(FREETYPE_MAJOR).arg(FREETYPE_MINOR).arg(FREETYPE_PATCH)
	 .arg(major).arg(minor).arg(patch),
	 QObject::tr("Dismiss"));
      FT_Done_FreeType(engine.iLib);
      engine.iOk = false;
      return 0;
    }
#endif
  }
  return new IpeCanvasFonts(aa, fontPool, services);
}

//! Private constructor
IpeCanvasFonts::IpeCanvasFonts(bool antiAlias, const IpeFontPool *fontPool,
			       IpeFontsServices *services)
  : iAntiAlias(antiAlias), iFontPool(fontPool), iServices(services)
{
  // nothing
}

IpeCanvasFonts::~IpeCanvasFonts()
{
  for (FontSeq::iterator it = iFonts.begin(); it != iFonts.end(); ++it)
    delete *it;
  for (FaceSeq::iterator it = iFaces.begin(); it != iFaces.end(); ++it)
    delete *it;
}

//! Get a typeface.
/*! Corresponds to a Freetype "face", or a PDF font resource.  An
  IpeFace can be loaded at various sizes (transformations), resulting
  in individual IpeFaceSize's. */
IpeFace *IpeCanvasFonts::GetFace(int id)
{
  for (FaceSeq::iterator it = iFaces.begin(); it != iFaces.end(); ++it) {
    if ((*it)->Matches(id))
      return *it;
  }
  // need to create it
  std::vector<IpeFont>::const_iterator it = iFontPool->begin();
  while (it != iFontPool->end() && it->iLatexNumber != id)
    ++it;
  if (it == iFontPool->end())
    return 0;
  IpeFace *face = new IpeFace(id, *it, iAntiAlias, iServices);
  iFaces.push_back(face);
  return face;
}

//! Get a font.
IpeFaceSize *IpeCanvasFonts::GetSize(int id, const IpeLinear &matrix)
{
  // is it in the cache?
  for (FontSeq::iterator it = iFonts.begin();
       it != iFonts.end(); ++it) {
    if ((*it)->Matches(id, matrix)) {
      // Reinsert it at front
      IpeFaceSize *font = *it;
      iFonts.erase(it);
      iFonts.push_front(font);
      return font;
    }
  }

  // get the face
  IpeFace *face = GetFace(id);
  if (!face) {
    ipeDebug("Failed to GetFace(%d)", id);
    return 0;
  }

  IpeFaceSize *font = new IpeFaceSize(face, matrix);
  if (!font)
    return 0;

#if 0
  // avoid cache becoming too big
  if (iFonts.size() >= xOutFontCacheSize) {
    delete iFonts.back();
    iFonts.pop_back();
  }
#endif

  // insert font in cache
  iFonts.push_front(font);
  return font;
}

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

IpeFace::IpeFace(int id, const IpeFont &font, bool antiAlias,
		 IpeFontsServices *services)
{
  ipeDebug("Loading face '%s'", font.iName.CString());
  iId = id;
  iAntiAlias = antiAlias;
  iData = font.iStreamData;
  iFace = 0; // must initialize!
  iType = font.iType;

  for (int i = 0; i < 0x100; ++i) {
    iGlyphIndex[i] = 0;
    iWidth[i] = 0;
  }

  if (!font.iStandardFont) {
    for (int i = 0; i < 0x100; ++i)
      iWidth[i] = font.iWidth[i];
    if (FT_New_Memory_Face(engine.iLib, (const uchar *) iData.data(),
			   iData.size(), 0, &iFace))
      return;
  } else {
    if (!getStdFontWidths(font.iName, font.iEncoding, iWidth))
      return;
    IpeBuffer data = services->StandardFont(font.iName);
    if (data.size() == 0 ||
	FT_New_Memory_Face(engine.iLib, (const uchar *) data.data(),
			   data.size(), 0, &iFace))
      return;
  }

  if (iType == IpeFont::EType1) {
    for (int i = 0; i < 0x100; ++i)
      iGlyphIndex[i] =
	FT_Get_Name_Index(iFace,
			  const_cast<char *>(font.iEncoding[i].CString()));
  } else {
    // Truetype font, don't use /Encoding (shouldn't have one)
    FT_Set_Charmap(iFace, iFace->charmaps[0]);
    if (iFace->charmaps[0]->platform_id != 1 ||
	iFace->charmaps[0]->encoding_id != 0) {
      ipeDebug("TrueType face %d has strange first charmap (of %d)",
	       iId, iFace->num_charmaps);
      for (int i = 0; i < iFace->num_charmaps; ++i) {
	ipeDebug("Map %d has platform %d, encoding %d",
		 i, iFace->charmaps[i]->platform_id,
		 iFace->charmaps[i]->encoding_id);
      }
    }
  }
}

IpeFace::~IpeFace()
{
  if (iFace)
    FT_Done_Face(iFace);
}

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

IpeFaceSize::IpeFaceSize(IpeFace *face, const IpeLinear &matrix)
  : iFace(face), iMatrix(matrix)
{
#if 0
  ipeDebug("Creating IpeFaceSize %d, m = %g %g %g %g",
	   face->iId, matrix.iA[0], matrix.iA[1], matrix.iA[2], matrix.iA[3]);
#endif

  iData = 0;
  FT_Face fface = iFace->iFace;
  if (!fface)
    return;

  // transform the four corners of the font bounding box -- the min
  // and max values form the bounding box of the transformed font
  IpeVector v[4];
  v[0] = IpeVector(fface->bbox.xMin, fface->bbox.yMin);
  v[1] = IpeVector(fface->bbox.xMin, fface->bbox.yMax);
  v[2] = IpeVector(fface->bbox.xMax, fface->bbox.yMin);
  v[3] = IpeVector(fface->bbox.xMax, fface->bbox.yMax);
  IpeRect bb;
  for (int i = 0; i < 4; ++i) {
    IpeVector p(matrix * v[i]);
    bb.AddPoint(p);
  }
  double div = fface->bbox.xMax > 20000 ? 65536 : 1;
  int xMin = int(bb.Min().iX / (div * fface->units_per_EM));
  int xMax = int(bb.Max().iX / (div * fface->units_per_EM));
  int yMin = int(bb.Min().iY / (div * fface->units_per_EM));
  int yMax = int(bb.Max().iY / (div * fface->units_per_EM));
#if 0
  ipeDebug("Computed bounding box [%d, %d] x [%d, %d]",
	   xMin, xMax, yMin, yMax);
#endif

  // this should be (max - min + 1), but we add some padding to
  // deal with rounding errors
  iGlyphW = xMax - xMin + 3;
  iGlyphH = yMax - yMin + 3;
  if (iFace->iAntiAlias)
    iGlyphSize = iGlyphW * iGlyphH;
  else
    iGlyphSize = ((iGlyphW + 7) >> 3) * iGlyphH;

  iData = new uchar[iGlyphSize];
}

IpeFaceSize::~IpeFaceSize()
{
  delete [] iData;
}

//! Retrieve rasterized glyph from cache, creating it if necessary.
/*! \a w and \a h are set to width and height of the resulting pixmap,
  \a x and \a y are set to the offset in the pixmap---that is,
  they have to be subtracted from the hotspot to get the coordinates
  of the top-left pixel to be transferred. */
uchar *IpeFaceSize::GetGlyph(int ch, int &x, int &y, int &w, int &h)
{
  if (!iFace->iFace)  // face is not available
    return 0;

  double size = (iMatrix * IpeVector(0,1)).Len();

  // compute the transform matrix
  FT_Matrix fm;
  fm.xx = (FT_Fixed)((iMatrix.iA[0] / size) * 65536);
  fm.yx = (FT_Fixed)((-iMatrix.iA[1] / size) * 65536);
  fm.xy = (FT_Fixed)((iMatrix.iA[2] / size) * 65536);
  fm.yy = (FT_Fixed)((-iMatrix.iA[3] / size) * 65536);

  if (FT_Set_Pixel_Sizes(iFace->iFace, 0, int(size)))
    return 0;
  FT_Set_Transform(iFace->iFace, &fm, 0);

  int idx = 0;
  if (iFace->Type() == IpeFont::EType1)
    idx = iFace->iGlyphIndex[ch];
  else
    idx = FT_Get_Char_Index(iFace->iFace, (FT_ULong) ch);

  if (idx == 0)
    return 0;

  // if we have the FT2 bytecode interpreter, autohinting won't be used
#ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER
  if (FT_Load_Glyph(iFace->iFace, idx, FT_LOAD_DEFAULT))
    return false;
#else
  // XPDF comment:
  // FT2's autohinting doesn't always work very well (especially with
  // font subsets), so turn it off if anti-aliasing is enabled; if
  // anti-aliasing is disabled, this seems to be a tossup - some fonts
  // look better with hinting, some without, so leave hinting on
  if (FT_Load_Glyph(iFace->iFace, idx,
		    iFace->iAntiAlias ? FT_LOAD_NO_HINTING
		    : FT_LOAD_DEFAULT))
    return 0;
#endif

  FT_GlyphSlot slot = iFace->iFace->glyph;
  if (FT_Render_Glyph(slot, iFace->iAntiAlias ? FT_RENDER_MODE_NORMAL :
		      FT_RENDER_MODE_MONO))
    return 0;
  x = -slot->bitmap_left;
  y = slot->bitmap_top;
  w = slot->bitmap.width;
  h = slot->bitmap.rows;
  if (w > iGlyphW || h > iGlyphH) {
    ipeDebug("Weird FreeType glyph size: %d > %d or %d > %d\n",
	     w, iGlyphW, h, iGlyphH);
    return 0;
  }

  int rowSize = iFace->iAntiAlias ? w : (w + 7) >> 3;
  int pitch = slot->bitmap.pitch;
  if (pitch < 0) pitch = -pitch;

  uchar *dst = iData;
  uchar *src = slot->bitmap.buffer;
  for (int y = 0; y < h; ++y) {
    memcpy(dst, src, rowSize);
    dst += rowSize;
    src += pitch;
  }
  return iData;
}

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