//========================================================================
//
// WebFont.cc
//
// Copyright 2019 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include "gmem.h"
#include "gmempp.h"
#include "GHash.h"
#include "FoFiTrueType.h"
#include "FoFiType1C.h"
#include "CharCodeToUnicode.h"
#include "WebFont.h"

WebFont::WebFont(GfxFont *gfxFontA, XRef *xref) {
  GfxFontType type;
  Ref id;

  gfxFont = gfxFontA;
  fontBuf = NULL;
  ffTrueType = NULL;
  ffType1C = NULL;
  isOpenType = gFalse;

  if (gfxFont->getEmbeddedFontID(&id)) {
    type = gfxFont->getType();
    if (type == fontTrueType ||
	type == fontTrueTypeOT ||
	type == fontCIDType2 ||
	type == fontCIDType2OT) {
      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
	ffTrueType = FoFiTrueType::make(fontBuf, fontLength, 0);
      }
    } else if (type == fontType1C ||
	       type == fontCIDType0C) {
      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
	ffType1C = FoFiType1C::make(fontBuf, fontLength);
      }
    } else if (type == fontType1COT ||
	       type == fontCIDType0COT) {
      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
	isOpenType = gTrue;
      }
    }
  }
}

WebFont::~WebFont() {
  delete ffTrueType;
  delete ffType1C;
  gfree(fontBuf);
}

GBool WebFont::canWriteTTF() {
  return ffTrueType != NULL;
}

GBool WebFont::canWriteOTF() {
  return ffType1C || isOpenType;
}

static void writeToFile(void *stream, const char *data, int len) {
  fwrite(data, 1, len, (FILE *)stream);
}

static void writeToString(void *stream, const char *data, int len) {
  ((GString *)stream)->append(data, len);
}

GBool WebFont::generateTTF(FoFiOutputFunc outFunc, void *stream) {
  int *codeToGID;
  Guchar *cmapTable;
  GBool freeCodeToGID;
  int nCodes, cmapTableLength;

  if (!ffTrueType) {
    return gFalse;
  }
  if (gfxFont->isCIDFont()) {
    codeToGID = ((GfxCIDFont *)gfxFont)->getCIDToGID();
    nCodes = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
    if (!codeToGID) {
      nCodes = ffTrueType->getNumGlyphs();
    }
    freeCodeToGID = gFalse;
  } else {
    codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ffTrueType);
    nCodes = 256;
    freeCodeToGID = gTrue;
  }
  cmapTable = makeUnicodeCmapTable(codeToGID, nCodes, &cmapTableLength);
  if (freeCodeToGID) {
    gfree(codeToGID);
  }
  if (!cmapTable) {
    return gFalse;
  }
  ffTrueType->writeTTF(outFunc, stream, NULL, NULL,
		       cmapTable, cmapTableLength);
  gfree(cmapTable);
  return gTrue;
}

GBool WebFont::writeTTF(const char *fontFilePath) {
  FILE *out = fopen(fontFilePath, "wb");
  if (!out) {
    return gFalse;
  }
  GBool ret = generateTTF(writeToFile, out);
  fclose(out);
  return ret;
}

GString *WebFont::getTTFData() {
  GString *s = new GString();
  if (!generateTTF(writeToString, s)) {
    delete s;
    return NULL;
  }
  return s;
}

GBool WebFont::generateOTF(FoFiOutputFunc outFunc, void *stream) {
  int *codeToGID;
  Gushort *widths;
  Guchar *cmapTable;
  int nCodes, nWidths, cmapTableLength;

  if (ffType1C) {
    if (gfxFont->getType() == fontType1C) {
      codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ffType1C);
      if (!(cmapTable = makeUnicodeCmapTable(codeToGID, 256,
					     &cmapTableLength))) {
	gfree(codeToGID);
	return gFalse;
      }
      widths = makeType1CWidths(codeToGID, 256, &nWidths);
      gfree(codeToGID);
    } else { // fontCIDType0C
      codeToGID = ffType1C->getCIDToGIDMap(&nCodes);
      if (!(cmapTable = makeUnicodeCmapTable(codeToGID, nCodes,
					     &cmapTableLength))) {
	gfree(codeToGID);
	return gFalse;
      }
      widths = makeCIDType0CWidths(codeToGID, nCodes, &nWidths);
      gfree(codeToGID);
    }
    ffType1C->convertToOpenType(outFunc, stream, nWidths, widths,
				cmapTable, cmapTableLength);
    gfree(cmapTable);
    gfree(widths);

  } else if (isOpenType) {
    outFunc(stream, fontBuf, fontLength);

  } else {
    return gFalse;
  }

  return gTrue;
}

GBool WebFont::writeOTF(const char *fontFilePath) {
  FILE *out = fopen(fontFilePath, "wb");
  if (!out) {
    return gFalse;
  }
  GBool ret = generateOTF(writeToFile, out);
  fclose(out);
  return ret;
}

GString *WebFont::getOTFData() {
  GString *s = new GString();
  if (!generateOTF(writeToString, s)) {
    delete s;
    return NULL;
  }
  return s;
}

Gushort *WebFont::makeType1CWidths(int *codeToGID, int nCodes,
				   int *nWidths) {
  Gushort *widths;
  Gushort width;
  int widthsLen, gid, i;

  widthsLen = ffType1C->getNumGlyphs();
  widths = (Gushort *)gmallocn(widthsLen, sizeof(Gushort));
  for (i = 0; i < widthsLen; ++i) {
    widths[i] = 0;
  }
  for (i = 0; i < nCodes; ++i) {
    gid = codeToGID[i];
    if (gid < 0 || gid >= widthsLen) {
      continue;
    }
    width = (Gushort)(((Gfx8BitFont *)gfxFont)->getWidth((Guchar)i)
		      * 1000 + 0.5);
    if (width == 0) {
      continue;
    }
    widths[gid] = width;
  }
  *nWidths = widthsLen;
  return widths;
}

Gushort *WebFont::makeCIDType0CWidths(int *codeToGID, int nCodes,
				      int *nWidths) {
  Gushort *widths;
  Gushort width;
  int widthsLen, gid, i;

  widthsLen = ffType1C->getNumGlyphs();
  widths = (Gushort *)gmallocn(widthsLen, sizeof(Gushort));
  for (i = 0 ; i < widthsLen; ++i) {
    widths[i] = 0;
  }
  for (i = 0; i < nCodes; ++i) {
    gid = codeToGID[i];
    if (gid < 0 || gid >= widthsLen) {
      continue;
    }
    width = (Gushort)(((GfxCIDFont *)gfxFont)->getWidth((CID)i)
		      * 1000 + 0.5);
    if (width == 0) {
      continue;
    }
    widths[gid] = width;
  }
  *nWidths = widthsLen;
  return widths;
}

Guchar *WebFont::makeUnicodeCmapTable(int *codeToGID, int nCodes,
				      int *unicodeCmapLength) {
  int *unicodeToGID;
  Guchar *cmapTable;
  int unicodeToGIDLength, nMappings, len;
  int nSegs, searchRange, entrySelector, rangeShift;
  int glyphIdOffset, idRangeOffset;
  int start, end, c, i;

  if (!(unicodeToGID = makeUnicodeToGID(codeToGID, nCodes,
					&unicodeToGIDLength))) {
    return NULL;
  }

  // count the valid code-to-glyph mappings, and the sequences of
  // consecutive valid mappings
  // (note: char code 65535 is used to mark the end of table)
  nMappings = 0;
  nSegs = 1; // count the last segment, mapping 65535
  for (c = 0; c < unicodeToGIDLength && c <= 65534; ++c) {
    if (unicodeToGID[c]) {
      ++nMappings;
      if (c == 0 || !unicodeToGID[c-1]) {
	++nSegs;
      }
    }
  }

  i = 1;
  entrySelector = 0;
  while (2 * i <= nSegs) {
    i *= 2;
    ++entrySelector;
  }
  searchRange = 1 << (entrySelector + 1);
  rangeShift = 2 * nSegs - searchRange;

  len = 28 + nSegs * 8 + nMappings * 2;
  cmapTable = (Guchar *)gmalloc(len);

  // header
  cmapTable[ 0] = 0x00;	// table version
  cmapTable[ 1] = 0x00;
  cmapTable[ 2] = 0x00;	// number of cmaps
  cmapTable[ 3] = 0x01;
  cmapTable[ 4] = 0x00;	// platform[0]
  cmapTable[ 5] = 0x03;
  cmapTable[ 6] = 0x00;	// encoding[0]
  cmapTable[ 7] = 0x01;
  cmapTable[ 8] = 0x00;	// offset[0]
  cmapTable[ 9] = 0x00;
  cmapTable[10] = 0x00;
  cmapTable[11] = 0x0c;

  // table info
  cmapTable[12] = 0x00;					// cmap format
  cmapTable[13] = 0x04;
  cmapTable[14] = (Guchar)((len - 12) >> 8);		// cmap length
  cmapTable[15] = (Guchar)(len - 12);
  cmapTable[16] = 0x00;					// cmap version
  cmapTable[17] = 0x00;
  cmapTable[18] = (Guchar)(nSegs >> 7);			// segCountX2
  cmapTable[19] = (Guchar)(nSegs << 1);
  cmapTable[20] = (Guchar)(searchRange >> 8);		// searchRange
  cmapTable[21] = (Guchar)searchRange;
  cmapTable[22] = (Guchar)(entrySelector >> 8);		// entrySelector
  cmapTable[23] = (Guchar)entrySelector;
  cmapTable[24] = (Guchar)(rangeShift >> 8);		// rangeShift
  cmapTable[25] = (Guchar)rangeShift;
  cmapTable[26 + nSegs*2    ] = 0;			// reservedPad
  cmapTable[26 + nSegs*2 + 1] = 0;

  i = 0;
  glyphIdOffset = 28 + nSegs*8;
  for (c = 0; c < unicodeToGIDLength && c <= 65534; ++c) {
    if (unicodeToGID[c]) {
      if (c == 0 || !unicodeToGID[c-1]) {
	start = c;
	cmapTable[28 + nSegs*2 + i*2    ] = (Guchar)(start >> 8);
	cmapTable[28 + nSegs*2 + i*2 + 1] = (Guchar)start;
	cmapTable[28 + nSegs*4 + i*2    ] = (Guchar)0;	// idDelta
	cmapTable[28 + nSegs*4 + i*2 + 1] = (Guchar)0;
	idRangeOffset = glyphIdOffset - (28 + nSegs*6 + i*2);
	cmapTable[28 + nSegs*6 + i*2    ] = (Guchar)(idRangeOffset >> 8);
	cmapTable[28 + nSegs*6 + i*2 + 1] = (Guchar)idRangeOffset;
      }
      if (c == 65534 || !unicodeToGID[c+1]) {
	end = c;
	cmapTable[26 + i*2    ] = (Guchar)(end >> 8);
	cmapTable[26 + i*2 + 1] = (Guchar)end;
	++i;
      }
      cmapTable[glyphIdOffset++] = (Guchar)(unicodeToGID[c] >> 8);
      cmapTable[glyphIdOffset++] = (Guchar)unicodeToGID[c];
    }
  }

  // last segment maps code 65535 to GID 0
  cmapTable[26 + i*2    ] = (Guchar)0xff;		// end
  cmapTable[26 + i*2 + 1] = (Guchar)0xff;
  cmapTable[28 + nSegs*2 + i*2    ] = (Guchar)0xff;	// start
  cmapTable[28 + nSegs*2 + i*2 + 1] = (Guchar)0xff;
  cmapTable[28 + nSegs*4 + i*2    ] = (Guchar)0;	// idDelta
  cmapTable[28 + nSegs*4 + i*2 + 1] = (Guchar)1;
  cmapTable[28 + nSegs*6 + i*2    ] = (Guchar)0;	// idRangeOffset
  cmapTable[28 + nSegs*6 + i*2 + 1] = (Guchar)0;

  gfree(unicodeToGID);

  *unicodeCmapLength = len;
  return cmapTable;
}

int *WebFont::makeUnicodeToGID(int *codeToGID, int nCodes,
			       int *unicodeToGIDLength) {
  int *unicodeToGID;
  CharCodeToUnicode *ctu;
  Unicode u[2];
  int len, size, newSize, uLen, c, gid;

  if (gfxFont->isCIDFont()) {
    if (!(ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) {
      return NULL;
    }
  } else {
    ctu = ((Gfx8BitFont *)gfxFont)->getToUnicode();
  }

  len = 0;
  size = 256;
  unicodeToGID = (int *)gmallocn(size, sizeof(int));
  memset(unicodeToGID, 0, size * sizeof(int));
  for (c = 0; c < nCodes; ++c) {
    gid = codeToGID ? codeToGID[c] : c;
    if (gid < 0 || gid >= 65536) {
      continue;
    }
    uLen = ctu->mapToUnicode(c, u, 2);
    if (uLen != 1) {
      continue;
    }
    if (u[0] >= 65536) {    // sanity check
      continue;
    }
    if ((int)u[0] >= size) {
      newSize = 2 * size;
      while ((int)u[0] >= newSize) {
	newSize *= 2;
      }
      unicodeToGID = (int *)greallocn(unicodeToGID, newSize, sizeof(int));
      memset(unicodeToGID + size, 0, (newSize - size) * sizeof(int));
      size = newSize;
    }
    unicodeToGID[u[0]] = gid;
    if ((int)u[0] >= len) {
      len = u[0] + 1;
    }
  }

  ctu->decRefCnt();

  *unicodeToGIDLength = len;
  return unicodeToGID;
}
