//========================================================================
//
// SplashFTFont.cc
//
// Copyright 2003-2013 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <ft2build.h>
#include FT_OUTLINE_H
#include FT_SIZES_H
#include FT_GLYPH_H
#include "gmem.h"
#include "SplashMath.h"
#include "SplashGlyphBitmap.h"
#include "SplashPath.h"
#include "SplashFontEngine.h"
#include "SplashFTFontEngine.h"
#include "SplashFTFontFile.h"
#include "SplashFTFont.h"

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

static int glyphPathMoveTo(const FT_Vector *pt, void *path);
static int glyphPathLineTo(const FT_Vector *pt, void *path);
static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt,
			    void *path);
static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2,
			    const FT_Vector *pt, void *path);

//------------------------------------------------------------------------
// SplashFTFont
//------------------------------------------------------------------------

SplashFTFont::SplashFTFont(SplashFTFontFile *fontFileA, SplashCoord *matA,
			   SplashCoord *textMatA):
  SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa)
{
  FT_Face face;
  int size, div;
  int x, y;
#if USE_FIXEDPOINT
  SplashCoord scale;
#endif

  face = fontFileA->face;
  if (FT_New_Size(face, &sizeObj)) {
    return;
  }
  face->size = sizeObj;
  size = splashRound(splashDist(0, 0, mat[2], mat[3]));
  if (size < 1) {
    size = 1;
  }
  if (FT_Set_Pixel_Sizes(face, 0, size)) {
    return;
  }
  // if the textMat values are too small, FreeType's fixed point
  // arithmetic doesn't work so well
  textScale = splashDist(0, 0, textMat[2], textMat[3]) / size;

  div = face->bbox.xMax > 20000 ? 65536 : 1;

#if USE_FIXEDPOINT
  scale = (SplashCoord)1 / (SplashCoord)face->units_per_EM;

  // transform the four corners of the font bounding box -- the min
  // and max values form the bounding box of the transformed font
  x = (int)(mat[0] * (scale * (face->bbox.xMin / div)) +
	    mat[2] * (scale * (face->bbox.yMin / div)));
  xMin = xMax = x;
  y = (int)(mat[1] * (scale * (face->bbox.xMin / div)) +
	    mat[3] * (scale * (face->bbox.yMin / div)));
  yMin = yMax = y;
  x = (int)(mat[0] * (scale * (face->bbox.xMin / div)) +
	    mat[2] * (scale * (face->bbox.yMax / div)));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)(mat[1] * (scale * (face->bbox.xMin / div)) +
	    mat[3] * (scale * (face->bbox.yMax / div)));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
  x = (int)(mat[0] * (scale * (face->bbox.xMax / div)) +
	    mat[2] * (scale * (face->bbox.yMin / div)));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)(mat[1] * (scale * (face->bbox.xMax / div)) +
	    mat[3] * (scale * (face->bbox.yMin / div)));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
  x = (int)(mat[0] * (scale * (face->bbox.xMax / div)) +
	    mat[2] * (scale * (face->bbox.yMax / div)));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)(mat[1] * (scale * (face->bbox.xMax / div)) +
	    mat[3] * (scale * (face->bbox.yMax / div)));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
#else // USE_FIXEDPOINT
  // transform the four corners of the font bounding box -- the min
  // and max values form the bounding box of the transformed font
  x = (int)((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) /
	    (div * face->units_per_EM));
  xMin = xMax = x;
  y = (int)((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) /
	    (div * face->units_per_EM));
  yMin = yMax = y;
  x = (int)((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMax) /
	    (div * face->units_per_EM));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMax) /
	    (div * face->units_per_EM));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
  x = (int)((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMin) /
	    (div * face->units_per_EM));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMin) /
	    (div * face->units_per_EM));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
  x = (int)((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMax) /
	    (div * face->units_per_EM));
  if (x < xMin) {
    xMin = x;
  } else if (x > xMax) {
    xMax = x;
  }
  y = (int)((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMax) /
	    (div * face->units_per_EM));
  if (y < yMin) {
    yMin = y;
  } else if (y > yMax) {
    yMax = y;
  }
#endif // USE_FIXEDPOINT
  // This is a kludge: some buggy PDF generators embed fonts with
  // zero bounding boxes.
  if (xMax == xMin) {
    xMin = 0;
    xMax = size;
  }
  if (yMax == yMin) {
    yMin = 0;
    yMax = (int)((SplashCoord)1.2 * size);
  }

  // compute the transform matrix
#if USE_FIXEDPOINT
  matrix.xx = (FT_Fixed)((mat[0] / size).get16Dot16());
  matrix.yx = (FT_Fixed)((mat[1] / size).get16Dot16());
  matrix.xy = (FT_Fixed)((mat[2] / size).get16Dot16());
  matrix.yy = (FT_Fixed)((mat[3] / size).get16Dot16());
  textMatrix.xx = (FT_Fixed)((textMat[0] / (textScale * size)).get16Dot16());
  textMatrix.yx = (FT_Fixed)((textMat[1] / (textScale * size)).get16Dot16());
  textMatrix.xy = (FT_Fixed)((textMat[2] / (textScale * size)).get16Dot16());
  textMatrix.yy = (FT_Fixed)((textMat[3] / (textScale * size)).get16Dot16());
#else
  matrix.xx = (FT_Fixed)((mat[0] / size) * 65536);
  matrix.yx = (FT_Fixed)((mat[1] / size) * 65536);
  matrix.xy = (FT_Fixed)((mat[2] / size) * 65536);
  matrix.yy = (FT_Fixed)((mat[3] / size) * 65536);
  textMatrix.xx = (FT_Fixed)((textMat[0] / (textScale * size)) * 65536);
  textMatrix.yx = (FT_Fixed)((textMat[1] / (textScale * size)) * 65536);
  textMatrix.xy = (FT_Fixed)((textMat[2] / (textScale * size)) * 65536);
  textMatrix.yy = (FT_Fixed)((textMat[3] / (textScale * size)) * 65536);
#endif
}

SplashFTFont::~SplashFTFont() {
}

GBool SplashFTFont::getGlyph(int c, int xFrac, int yFrac,
			     SplashGlyphBitmap *bitmap) {
  return SplashFont::getGlyph(c, xFrac, 0, bitmap);
}

GBool SplashFTFont::makeGlyph(int c, int xFrac, int yFrac,
			      SplashGlyphBitmap *bitmap) {
  SplashFTFontFile *ff;
  FT_Vector offset;
  FT_GlyphSlot slot;
  FT_UInt gid;
  FT_Int32 flags;
  int rowSize;
  Guchar *p, *q;
  int i;

  ff = (SplashFTFontFile *)fontFile;

  ff->face->size = sizeObj;
  offset.x = (FT_Pos)(int)((SplashCoord)xFrac * splashFontFractionMul * 64);
  offset.y = 0;
  FT_Set_Transform(ff->face, &matrix, &offset);
  slot = ff->face->glyph;

  if (ff->codeToGID && c < ff->codeToGIDLen) {
    gid = (FT_UInt)ff->codeToGID[c];
  } else {
    gid = (FT_UInt)c;
  }
  if (ff->trueType && gid < 0) {
    // skip the TrueType notdef glyph
    return gFalse;
  }

  // Set up the load flags:
  // * disable bitmaps because they look ugly when scaled, rotated,
  //   etc.
  // * disable autohinting because it can fail badly with font subsets
  //   that use invalid glyph names (the FreeType autohinter depends
  //   on the glyph name to figure out how to autohint the glyph)
  // * but enable light autohinting for Type 1 fonts because regular
  //   hinting looks pretty bad, and the invalid glyph name issue
  //   seems to be very rare (Type 1 fonts are mostly used for
  //   substitution, in which case the full font is being used, which
  //   means we have the glyph names)
  flags = FT_LOAD_NO_BITMAP;
  if (ff->engine->flags & splashFTNoHinting) {
    flags |= FT_LOAD_NO_HINTING;
  } else if (ff->useLightHinting) {
    flags |= FT_LOAD_TARGET_LIGHT;
  } else {
    flags |= FT_LOAD_NO_AUTOHINT;
  }
  if (FT_Load_Glyph(ff->face, gid, flags)) {
    return gFalse;
  }
  if (FT_Render_Glyph(slot, aa ? FT_RENDER_MODE_NORMAL
		               : FT_RENDER_MODE_MONO)) {
    return gFalse;
  }
  if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
    // this can happen if (a) the glyph is really tiny or (b) the
    // metrics in the TrueType file are broken
    return gFalse;
  }

  bitmap->x = -slot->bitmap_left;
  bitmap->y = slot->bitmap_top;
  bitmap->w = slot->bitmap.width;
  bitmap->h = slot->bitmap.rows;
  bitmap->aa = aa;
  if (aa) {
    rowSize = bitmap->w;
  } else {
    rowSize = (bitmap->w + 7) >> 3;
  }
  bitmap->data = (Guchar *)gmallocn(bitmap->h, rowSize);
  bitmap->freeData = gTrue;
  for (i = 0, p = bitmap->data, q = slot->bitmap.buffer;
       i < bitmap->h;
       ++i, p += rowSize, q += slot->bitmap.pitch) {
    memcpy(p, q, rowSize);
  }

  return gTrue;
}

struct SplashFTFontPath {
  SplashPath *path;
  SplashCoord textScale;
  GBool needClose;
};

SplashPath *SplashFTFont::getGlyphPath(int c) {
  static FT_Outline_Funcs outlineFuncs = {
#if FREETYPE_MINOR <= 1
    (int (*)(FT_Vector *, void *))&glyphPathMoveTo,
    (int (*)(FT_Vector *, void *))&glyphPathLineTo,
    (int (*)(FT_Vector *, FT_Vector *, void *))&glyphPathConicTo,
    (int (*)(FT_Vector *, FT_Vector *, FT_Vector *, void *))&glyphPathCubicTo,
#else
    &glyphPathMoveTo,
    &glyphPathLineTo,
    &glyphPathConicTo,
    &glyphPathCubicTo,
#endif
    0, 0
  };
  SplashFTFontFile *ff;
  SplashFTFontPath path;
  FT_GlyphSlot slot;
  FT_UInt gid;
  FT_Glyph glyph;

  ff = (SplashFTFontFile *)fontFile;
  ff->face->size = sizeObj;
  FT_Set_Transform(ff->face, &textMatrix, NULL);
  slot = ff->face->glyph;
  if (ff->codeToGID && c < ff->codeToGIDLen) {
    gid = ff->codeToGID[c];
  } else {
    gid = (FT_UInt)c;
  }
  if (ff->trueType && gid < 0) {
    // skip the TrueType notdef glyph
    return NULL;
  }
  if (FT_Load_Glyph(ff->face, gid, FT_LOAD_NO_BITMAP)) {
    return NULL;
  }
  if (FT_Get_Glyph(slot, &glyph)) {
    return NULL;
  }
  path.path = new SplashPath();
  path.textScale = textScale;
  path.needClose = gFalse;
  FT_Outline_Decompose(&((FT_OutlineGlyph)glyph)->outline,
		       &outlineFuncs, &path);
  if (path.needClose) {
    path.path->close();
  }
  FT_Done_Glyph(glyph);
  return path.path;
}

static int glyphPathMoveTo(const FT_Vector *pt, void *path) {
  SplashFTFontPath *p = (SplashFTFontPath *)path;

  if (p->needClose) {
    p->path->close();
    p->needClose = gFalse;
  }
  p->path->moveTo((SplashCoord)pt->x * p->textScale / 64.0,
		  (SplashCoord)pt->y * p->textScale / 64.0);
  return 0;
}

static int glyphPathLineTo(const FT_Vector *pt, void *path) {
  SplashFTFontPath *p = (SplashFTFontPath *)path;

  p->path->lineTo((SplashCoord)pt->x * p->textScale / 64.0,
		  (SplashCoord)pt->y * p->textScale / 64.0);
  p->needClose = gTrue;
  return 0;
}

static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt,
			    void *path) {
  SplashFTFontPath *p = (SplashFTFontPath *)path;
  SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;

  if (!p->path->getCurPt(&x0, &y0)) {
    return 0;
  }
  xc = (SplashCoord)ctrl->x * p->textScale / 64.0;
  yc = (SplashCoord)ctrl->y * p->textScale / 64.0;
  x3 = (SplashCoord)pt->x * p->textScale / 64.0;
  y3 = (SplashCoord)pt->y * p->textScale / 64.0;

  // A second-order Bezier curve is defined by two endpoints, p0 and
  // p3, and one control point, pc:
  //
  //     p(t) = (1-t)^2*p0 + t*(1-t)*pc + t^2*p3
  //
  // A third-order Bezier curve is defined by the same two endpoints,
  // p0 and p3, and two control points, p1 and p2:
  //
  //     p(t) = (1-t)^3*p0 + 3t*(1-t)^2*p1 + 3t^2*(1-t)*p2 + t^3*p3
  //
  // Applying some algebra, we can convert a second-order curve to a
  // third-order curve:
  //
  //     p1 = (1/3) * (p0 + 2pc)
  //     p2 = (1/3) * (2pc + p3)

  x1 = (SplashCoord)(1.0 / 3.0) * (x0 + (SplashCoord)2 * xc);
  y1 = (SplashCoord)(1.0 / 3.0) * (y0 + (SplashCoord)2 * yc);
  x2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * xc + x3);
  y2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * yc + y3);

  p->path->curveTo(x1, y1, x2, y2, x3, y3);
  p->needClose = gTrue;
  return 0;
}

static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2,
			    const FT_Vector *pt, void *path) {
  SplashFTFontPath *p = (SplashFTFontPath *)path;

  p->path->curveTo((SplashCoord)ctrl1->x * p->textScale / 64.0,
		   (SplashCoord)ctrl1->y * p->textScale / 64.0,
		   (SplashCoord)ctrl2->x * p->textScale / 64.0,
		   (SplashCoord)ctrl2->y * p->textScale / 64.0,
		   (SplashCoord)pt->x * p->textScale / 64.0,
		   (SplashCoord)pt->y * p->textScale / 64.0);
  p->needClose = gTrue;
  return 0;
}

#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
