//========================================================================
//
// TrueType.cc - TrueType fonts handling
//
// Copyright 1999 Emmanuel Lesueur
//
//========================================================================


#ifdef __GNUC__
#pragma implementation
#endif

#define DB(x) //x

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#include <setjmp.h>
#include "gmem.h"
#include "GString.h"
#include "Object.h"
#include "Stream.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "FontFile.h"
#include "FontEncoding.h"
#include "Error.h"
#include "TextOutputDev.h"
#include "FontOutputDev.h"
#include "AOutputDev.h"
#include "type1/pictures.h"
#include "freetype/ftnameid.h"

class Engine {
public:
  Engine() : engineOk(false) {
    if(TT_Init_FreeType(&engine) || TT_Init_Post_Extension(engine))
      printf("Can't initialize TrueType engine\n");
    else
      engineOk = true;
  }
  ~Engine() {
    if (engineOk)
      TT_Done_FreeType(engine);
  }
  bool isOk() const { return engineOk; }
  operator TT_Engine () const { return engine; }
private:
  Engine(const Engine&);
  Engine& operator = (const Engine&);
  TT_Engine engine;
  bool engineOk;
};

Engine engine;


extern "C" {
  extern jmp_buf stck_state;
  extern XYspace *IDENTITY;
  char *t1_get_abort_message(int);
}
extern FontEncoding macRomanEncoding;
extern FontEncoding winAnsiEncoding;

//------------------------------------------------------------------------
// ATTBaseFont
//------------------------------------------------------------------------

TTBaseFont::TTBaseFont(const char *fileName, EncodingID encID) :
  BaseFont(fileName), ok(gFalse), stdEnc(NULL), buf(NULL)
{
  char tmpname[32];

  if (!engine.isOk())
    return;

  memset(index, 0, sizeof(index));

  if(fileName[0]=='\001' && fileName[1]==':' && fileName[2]==':') {
    unsigned x = 0;
    fileName += 3;
    int n = 0;
    while (*fileName != ':') {
      x |= *fileName++ - '0' << n;
      n += 3;
    }
    unsigned y = 0;
    ++fileName;
    n = 0;
    while (*fileName != '\0') {
      y |= *fileName++ - '0' << n;
      n += 3;
    }
    char* oldbuf = (char*)x;
    size_t len = y;
    buf = (char*)gmalloc(len);
    memcpy(buf, oldbuf, len);

    char *t = tmpname;
    *t++ = '\001';
    *t++ = ':';
    *t++ = ':';
    x = unsigned(buf);
    while(x) {
	*t++ = char('0' + (x & 7));
	x >>= 3;
    }
    *t++ = ':';
    x = len;
    while(x) {
	*t++ = char('0' + (x & 7));
	x >>= 3;
    }
    *t = '\0';
    fileName = tmpname;
  }

  /*if(myFILE* f=myfopen(fileName,"rb")) {
      if(FILE* f2=fopen("sd0:font.ttf","wb")) {
	  printf("writing font\n");
	  do {
	    static char buf[1024];
	    int sz=myfread(buf,1,sizeof(buf),f);
	    if(sz<=0)
		break;
	    fwrite(buf,1,sz,f2);
	  } while(true);
	  fclose(f2);
      }
      myfclose(f);
  }*/

  if (TT_Error err = TT_Open_Face(engine, fileName, &face)) {
    error(-1, "TT: error 0x%lx opening font \"%s\"", err, *fileName == '\001' ? getName() : fileName);
    return;
  }

  /*TT_String *str;
  TT_UShort len;
  if (!TT_Get_Name_String(face, TT_NAME_ID_FULL_NAME, &str, &len)) {
    name.append(str,len);
  }*/

  if (TT_Error err = TT_Get_Face_Properties(face, &properties))
    error(-1, "TT: Error 0x%lx looking for face properties in font \"%s\".", err, getName());

  int i;
  for (i = 0; i < properties.num_CharMaps; ++i) {
    TT_UShort platformID, encodingID;
    if (TT_Error err = TT_Get_CharMap_ID(face, i, &platformID, &encodingID))
      error(-1, "TT: Error 0x%lx looking for charmap", err);
    else if (platformID == TT_PLATFORM_MACINTOSH &&
	     encodingID == TT_MAC_ID_ROMAN) {
      stdEnc = &macRomanEncoding;
      break;
    } else if (platformID == TT_PLATFORM_MICROSOFT &&
	       encodingID == TT_MS_ID_UNICODE_CS) {
      stdEnc = &winAnsiEncoding;
      break;
    }
  }
  if (i == properties.num_CharMaps) {
    if (TT_Error err = TT_Load_PS_Names(face, &post)) {
      error(-1, "TT: error 0x%lx looking for char names in font \"%s\".", err, getName());
      return;
    }
  } else {
    if (TT_Error err = TT_Get_CharMap(face, i, &charMap)) {
      error(-1, "TT: error 0x%lx looking for CharMap in font \"%s\".", err, getName());
      return;
    }
  }

  ok = gTrue;
}


TTBaseFont::~TTBaseFont() {
  clearCache();
  if (ok)
    TT_Close_Face(face);
  gfree(buf);
}

GBool TTBaseFont::isOk() {
  return ok;
}

void TTBaseFont::loadEncoding() {
  if (stdEnc) {
    for (int i = 0; i < 256; ++i)
      if (char *name = stdEnc->getCharName(i))
	encoding->addChar(i, copyString((char*)name));
  } else {
    if (TT_Error err = TT_Get_CharMap(face, 0, &charMap)) {
      error(-1, "TT: error 0x%lx looking for CharMap in font \"%s\".", err, getName());
      return;
    }
    for (int i = 0; i < 256; ++i) {
      if (TT_UShort index = TT_Char_Index(charMap, i)) {
	TT_String *name;
	if (TT_Error err = TT_Get_PS_Name(face, index, &name))
	  error(-1, "TT: error 0x%lx looking for glyph name", err);
	else if(strcmp(name, ".notdef")) {
	  encoding->addChar(i, copyString((char*)name));
	}
      }
    }
  }
}

void TTBaseFont::buildCharMap(FontEncoding *enc, short *map) {
  if (stdEnc) {
    for (int i = 0; i < 256; ++i) {
      if (TT_UShort index = TT_Char_Index(charMap, i)) {
	if (char *name = stdEnc->getCharName(i)) {
	  int code = enc->getCharCode(name);
	  if (code >= 0 && code < 256)
	    map[code] = index;
	}
      }
    }
  } else {
    GBool hasGlyph = gFalse;
    for (int i = 0; i < properties.num_Glyphs; ++i) {
      TT_String *name;
      if (TT_Error err = TT_Get_PS_Name(face, i, &name))
	error(-1, "TT: error 0x%lx looking for glyph name", err);
      else if (strcmp(name, ".notdef")) {
	int code = enc->getCharCode(name);
	if (code >= 0 && code < 256) {
	  map[code] = i;
	  hasGlyph = gTrue;
	}
      }
    }
    if (!hasGlyph)
      error(-1, "TT: No supported encoding in \"%s\"", getName());
  }
}

OutputFont *TTBaseFont::create(double m11, double m12, double m21, double m22) {
  return new TTOutputFont(this, m11, m12, m21, m22);
}

//------------------------------------------------------------------------
// TTOutputFont
//------------------------------------------------------------------------

TTOutputFont::TTOutputFont(TTBaseFont *base1,
			   double m11, double m12,
			   double m21, double m22):
  AOutputFont(base1, m11, m12, m21, m22), ok(gFalse),
  charSpaceLocal(NULL), glyphs(NULL)
{
  TT_Error err;
  double sz = sqrt(m21 * m21 + m22 * m22);

  sz *= 64;

  if ((err = TT_New_Instance(base1->face, &instance)) ||
      (err = TT_Set_Instance_Resolutions(instance, 72, 72)) ||
      (err = TT_Set_Instance_CharSize(instance, TT_F26Dot6(sz)))) {
    error(-1, "TT: error 0x%lx creating instance of font \"%s\"", err, base1->getName());
    return;
  }

  if (int err = setjmp(stck_state)) {
    error(-1, "T1 Error: %s\n", t1_get_abort_message(err));
    return;
  }

  // setup charSpaceLocal for this font
  charSpaceLocal = (XYspace *) Permanent(Transform(IDENTITY,
    m11 / sz, m12 / sz, m21 / sz, m22 / sz));

  // allocate the glyph array
  glyphs = new Glyph [256];
}

TTOutputFont::~TTOutputFont() {
  if (glyphs) {
    delete [] glyphs;
    TT_Done_Instance(instance);
  }
  if (charSpaceLocal)
    KillSpace(charSpaceLocal);
}

GBool TTOutputFont::isOk() {
  return glyphs != NULL;
}


// taken from ttf2pfb (from Chun-Yu Lee) in the FreeType package
static segment *curveto(segment *path, XYspace *space,
			TT_Outline& outline,
			TT_F26Dot6 x, TT_F26Dot6 y,
			int s, int e,
			TT_F26Dot6& lastx, TT_F26Dot6& lasty) {
  int  N, i;
  TT_F26Dot6 sx[3], sy[3], cx[4], cy[4];

  N = e - s + 1;
  cx[0] = lastx; cy[0] = lasty;
  if (s == e) {
    cx[1] = (2 * outline.points[s].x + outline.points[s - 1].x) / 3;
    cy[1] = (2 * outline.points[s].y + outline.points[s - 1].y) / 3;
    cx[2] = (2 * outline.points[s].x + x) / 3;
    cy[2] = (2 * outline.points[s].y + y) / 3;
    cx[3] = x;
    cy[3] = y;
    path = Join(path, Bezier(Loc(space, cx[1] - cx[0], cy[1] - cy[0]),
			     Loc(space, cx[2] - cx[0], cy[2] - cy[0]),
			     Loc(space, cx[3] - cx[0], cy[3] - cy[0])));
    /*path = Join(path, Bezier(Loc(space, cx[1] - cx[0], cy[1] - cy[0]),
			     Loc(space, cx[2] - cx[1], cy[2] - cy[1]),
			     Loc(space, cx[3] - cx[2], cy[3] - cy[2])));*/
  } else {
    for (i = 0; i < N; i++) {
      sx[0] = i == 0 ? outline.points[s - 1].x :
	(outline.points[i + s].x + outline.points[i + s - 1].x) / 2;
      sy[0] = i == 0 ? outline.points[s - 1].y :
	(outline.points[i + s].y + outline.points[i + s - 1].y) / 2;
      sx[1] = outline.points[s + i].x;
      sy[1] = outline.points[s + i].y;
      sx[2] = i == N - 1 ? x : (outline.points[s + i].x + outline.points[s + i + 1].x) / 2;
      sy[2] = i == N - 1 ? y : (outline.points[s + i].y + outline.points[s + i + 1].y) / 2;
      cx[1] = (2 * sx[1] + sx[0]) / 3;
      cy[1] = (2 * sy[1] + sy[0]) / 3;
      cx[2] = (2 * sx[1] + sx[2]) / 3;
      cy[2] = (2 * sy[1] + sy[2]) / 3;
      cx[3] = sx[2];
      cy[3] = sy[2];
      path = Join(path, Bezier(Loc(space, cx[1] - cx[0], cy[1] - cy[0]),
			       Loc(space, cx[2] - cx[0], cy[2] - cy[0]),
			       Loc(space, cx[3] - cx[0], cy[3] - cy[0])));
      /*path = Join(path, Bezier(Loc(space, cx[1] - cx[0], cy[1] - cy[0]),
			       Loc(space, cx[2] - cx[1], cy[2] - cy[1]),
			       Loc(space, cx[3] - cx[2], cy[3] - cy[2])));*/
      cx[0] = cx[3]; cy[0] = cy[3];
    }
  }
  lastx = x;
  lasty = y;
}

segment *TTOutputFont::getT1Outline(int c) {
  Glyph& g = glyphs[c];
  if (g.outline)
    return g.outline;
  TT_Glyph glyph;
  if (TT_New_Glyph(((TTBaseFont*)base)->face, &glyph))
    return NULL;
  TT_Outline outline;
  TT_Error err;
  if ((err = TT_Load_Glyph(instance, glyph, c, TTLOAD_DEFAULT)) ||
      (err = TT_Get_Glyph_Outline(glyph, &outline))) {
    error(-1, "TT: error 0x%lx loading glyph %d in font \"%s\"", err, c, base->getName());
    TT_Done_Glyph(glyph);
    return NULL;
  }

  // The following converts the ttf outline into a type1 outline.
  // This was adapted from Chun-Yu Lee's ttf2pfb.
  XYspace *space = charSpaceLocal;
  segment *path = NULL;
  int    idx, i, j;
  int start_offpt, end_offpt = 0, fst;
  TT_Pos lastx = 0;
  TT_Pos lasty = 0;

  for (i = 0, j = 0; i < outline.n_contours; i++) {
    fst = j;
    segment *p = Loc(space, outline.points[j].x - lastx, outline.points[j].y - lasty);
    lastx = outline.points[j].x;
    lasty = outline.points[j].y;
    if (path)
      path = Join(path, p);
    else
      path = p;
    j++;

    start_offpt = 0; /*start at least 1*/
    /* data pts for all contours stored in one array.
       each round j init at last j + 1 */

    /* start_offpt means start of off points.
       0 means no off points in record.
       N means the position of the off point.
       end_offpt means the ending off point.
       lastx, lasty is the last ON point from which Curve and Line
       shall start.
     */

    /* start with j=0. into loop, j=1.
       if pt[1] off, if start_offpt == 0, toggle start_offpt
	   next j=2. if on, now start_off != 0, run Curveto.
       if pt[1] on, start_off == 0, will run Lineto.
     */
    for (; j <= outline.contours[i]; j++) {
      if (!(outline.flags[j] & 1)) {
	if (!start_offpt) {
	  start_offpt = end_offpt = j;
	} else
	  end_offpt++;
      } else {                    /*On Curve*/
	if (start_offpt) {
	  /* start_offpt stuck at j, end_offpt++.
	     end_offpt - start_offpt gives no of off pts.
	     start_offpt gives start of sequence.
	     why need outline.xCoord[j] outline.yCoord[j]?
	   */
	  path = curveto(path, space, outline,
			 outline.points[j].x, outline.points[j].y,
			 start_offpt, end_offpt, lastx, lasty);
	  start_offpt = 0;

	  /* also use start_offpt as indicator to save one variable!!
	     after curveto, reset condition. */
	} else {
	  path = Join(path, Line(Loc(space, outline.points[j].x - lastx, outline.points[j].y - lasty)));
	  lastx = outline.points[j].x;
	  lasty = outline.points[j].y;
	}
      }
    }
    /* looks like closepath fst = first, i.e. go back to first */
    if (start_offpt) {
      path = curveto(path, space, outline,
		     outline.points[fst].x, outline.points[fst].y,
		     start_offpt, end_offpt, lastx, lasty);
    }/* else*/ {
      segment *cur = Phantom(path);
      path = ClosePath(path);
      path = Join(Snap(path), cur);
    }
  }
  //if (path)
    //path = ClosePath(path);
  TT_Done_Glyph(glyph);
  g.outline = path;
  return path;
}

region *TTOutputFont::getCharInterior(int c) {
  if (c < 0 || c > 255)
    return NULL;
  Glyph& g = glyphs[c];
  region *area = g.region;
  if (!area) {
    if (int err = setjmp(stck_state)) {
      error(-1, "T1 Error: %s\n", t1_get_abort_message(err));
      return NULL;
    }
    if(segment *path = getT1Outline(c))
      area = Interior(Dup(path), CONTINUITY + WINDINGRULE);
    g.region = area;
  }
  return area;
}

segment *TTOutputFont::getCharOutline(int c) {
  if (c < 0 || c > 255)
    return NULL;
  Glyph& g = glyphs[c];
  segment *path = g.outline;
  if (!path) {
    if (int err = setjmp(stck_state)) {
      error(-1, "T1 Error: %s\n", t1_get_abort_message(err));
      return NULL;
    }
    g.outline = path = getT1Outline(c);
  }
  return path;
}

