/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

    Copyright (C) 2002-2018 by Jin-Hwan Cho and Shunsaku Hirata,
    the dvipdfmx project team.
    
    This program 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.
    
    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
 * Type0 font support:
 * 
 * TODO:
 *
 *  Composite font (multiple descendants) - not supported in PDF
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "system.h"
#include "mem.h"
#include "error.h"
#include "dpxconf.h"
#include "dpxfile.h"

#include "pdfobj.h"
#include "fontmap.h"

#include "cmap.h"
#include "cid.h"

#include "type0.h"


#define TYPE0FONT_DEBUG_STR "Type0"
#define TYPE0FONT_DEBUG     3

static pdf_obj *pdf_read_ToUnicode_file (const char *cmap_name);

/*
 * used_chars:
 *
 *  Single bit is used for each CIDs since used_chars can be reused as a
 *  stream content of CIDSet by doing so. See, cid.h for add_to_used() and
 *  is_used().
 */

static char *
new_used_chars2(void)
{
  char *used_chars;

  used_chars = NEW(8192, char);
  memset(used_chars, 0, 8192);

  return used_chars;
}

#define FLAG_NONE              0
#define FLAG_USED_CHARS_SHARED (1 << 0)

struct Type0Font {
  char    *fontname;   /* BaseFont */
  char    *encoding;   /* "Identity-H" or "Identity-V" (not ID) */
  char    *used_chars; /* Used chars (CIDs) */

  /*
   * Type0 only
   */
  CIDFont *descendant; /* Only single descendant is allowed. */
  int      flags;
  int      wmode;
  int      cmap_id;

  /*
   * PDF Font Resource
   */
  pdf_obj *indirect;
  pdf_obj *fontdict;
  pdf_obj *descriptor; /* MUST BE NULL */
};

static void
Type0Font_init_font_struct (Type0Font *font)
{
  ASSERT(font);

  font->fontname   = NULL;
  font->fontdict   = NULL;
  font->indirect   = NULL;
  font->descriptor = NULL;
  font->encoding   = NULL;
  font->used_chars = NULL;
  font->descendant = NULL;
  font->wmode      = -1;
  font->cmap_id    = -1;
  font->flags      = FLAG_NONE;

  return;
}

static void
Type0Font_clean (Type0Font *font)
{
  if (font) {
    if (font->fontdict)
      ERROR("%s: Object not flushed.", TYPE0FONT_DEBUG_STR);
    if (font->indirect)
      ERROR("%s: Object not flushed.", TYPE0FONT_DEBUG_STR);
    if (font->descriptor)
      ERROR("%s: FontDescriptor unexpected for Type0 font.", TYPE0FONT_DEBUG_STR);
    if (!(font->flags & FLAG_USED_CHARS_SHARED) && font->used_chars)
      RELEASE(font->used_chars);
    if (font->encoding)
      RELEASE(font->encoding);
    if (font->fontname)
      RELEASE(font->fontname);
    font->fontdict   = NULL;
    font->indirect   = NULL;
    font->descriptor = NULL;
    font->used_chars = NULL;
    font->encoding   = NULL;
    font->fontname   = NULL;
  }
}

/* PLEASE FIX THIS */
#include "tt_cmap.h"

/* Try to load ToUnicode CMap from file system first, if not found fallback to
 * font CMap reverse lookup.
 * CHANGED: CMap here is not always Unicode to CID mapping. Don't use reverse lookup.
 */
static pdf_obj *
Type0Font_try_load_ToUnicode_stream(Type0Font *font, char *cmap_base) {
  pdf_obj *tounicode;
  char    *cmap_name;

  cmap_name = NEW(strlen(cmap_base)+strlen("-UTF16")+1, char);
  sprintf(cmap_name, "%s-UTF16", cmap_base);
  tounicode = pdf_read_ToUnicode_file(cmap_name);
  if (!tounicode) {
    sprintf(cmap_name, "%s-UCS2", cmap_base);
    tounicode = pdf_read_ToUnicode_file(cmap_name);
  }
  RELEASE(cmap_name);

  if (!tounicode) {
    CIDFont *cidfont = font->descendant;
    tounicode = otf_create_ToUnicode_stream(CIDFont_get_ident(cidfont),
                                            CIDFont_get_opt_index(cidfont),
                                            CIDFont_get_fontname(cidfont),
                                            Type0Font_get_usedchars(font));   
  }

  return tounicode;
}

static void
Type0Font_attach_ToUnicode_stream (Type0Font *font)
{
  pdf_obj    *tounicode;
  CIDFont    *cidfont;
  CIDSysInfo *csi;
  char       *fontname;

  /*
   * ToUnicode CMap:
   *
   *  ToUnicode CMaps are usually not required for standard character
   *  collections such as Adobe-Japan1. Identity-H is used for UCS
   *  ordering CID-keyed fonts. External resource must be loaded for
   *  others.
   */

  cidfont = font->descendant;
  if (!cidfont) {
    ERROR("%s: No descendant CID-keyed font.", TYPE0FONT_DEBUG_STR);
    return;
  }

  if (CIDFont_is_ACCFont(cidfont)) {
    /* No need to embed ToUnicode */
    return;
  } else if (CIDFont_is_UCSFont(cidfont)) {
    /*
     * Old version of dvipdfmx mistakenly used Adobe-Identity as Unicode.
     */
    tounicode = pdf_read_ToUnicode_file("Adobe-Identity-UCS2");
    if (!tounicode) { /* This should work */
      tounicode = pdf_new_name("Identity-H");
    }
    pdf_add_dict(font->fontdict, pdf_new_name("ToUnicode"), tounicode);
    return;
  }

  tounicode = NULL;
  csi       = CIDFont_get_CIDSysInfo(cidfont);
  fontname  = CIDFont_get_fontname(cidfont);
  if (CIDFont_get_embedding(cidfont)) {
    fontname += 7; /* FIXME: Skip pseudo unique tag... */
  }

  if (!strcmp(csi->registry, "Adobe") && !strcmp(csi->ordering, "Identity")) {
    switch (CIDFont_get_subtype(cidfont)) {
    case CIDFONT_TYPE2:
    /* PLEASE FIX THIS */
      {
        tounicode = otf_create_ToUnicode_stream(CIDFont_get_ident(cidfont),
                                                CIDFont_get_opt_index(cidfont),
                                                CIDFont_get_fontname(cidfont),
                                                Type0Font_get_usedchars(font));
      }
      break;
    default:
      if (CIDFont_get_flag(cidfont, CIDFONT_FLAG_TYPE1C)) {
        tounicode = otf_create_ToUnicode_stream(CIDFont_get_ident(cidfont),
                                                CIDFont_get_opt_index(cidfont),
                                                CIDFont_get_fontname(cidfont),
                                                Type0Font_get_usedchars(font));
      } else if (CIDFont_get_flag(cidfont, CIDFONT_FLAG_TYPE1)) {
        /* FIXME: handled on very different timing.
         * Font loader will create ToUnicode and set.
         */
        return;
      } else {
        tounicode = Type0Font_try_load_ToUnicode_stream(font, fontname);
      }
      break;
    }
  } else {
    char *cmap_base = NEW(strlen(csi->registry) + strlen(csi->ordering) + 2, char);
    sprintf(cmap_base, "%s-%s", csi->registry, csi->ordering);
    tounicode = Type0Font_try_load_ToUnicode_stream(font, cmap_base);
    RELEASE(cmap_base);
  }

  if (tounicode) {
    pdf_add_dict(font->fontdict,
                 pdf_new_name("ToUnicode"), tounicode);
  } else {
#if defined(LIBDPX)
    if (dpx_conf.verbose_level > 0)
      WARN("Failed to load ToUnicode CMap for font \"%s\"", fontname);
#else
    WARN("Failed to load ToUnicode CMap for font \"%s\"", fontname);
#endif /* LIBDPX */
  }

  return;
}

void
Type0Font_set_ToUnicode (Type0Font *font, pdf_obj *cmap_ref)
{
  ASSERT(font);

  pdf_add_dict(font->fontdict,
               pdf_new_name("ToUnicode"), cmap_ref);
}

static void
Type0Font_dofont (Type0Font *font)
{
  if (!font || !font->indirect)
    return;

  /* FIXME: Should move to pdffont.c */
  if (!pdf_lookup_dict(font->fontdict, "ToUnicode")) {
    Type0Font_attach_ToUnicode_stream(font);
  }
}

static void
Type0Font_flush (Type0Font *font)
{
  if (font) {
    if (font->fontdict)
      pdf_release_obj(font->fontdict);
    font->fontdict = NULL;
    if (font->indirect)
      pdf_release_obj(font->indirect);
    font->indirect = NULL;
    if (font->descriptor)
      ERROR("%s: FontDescriptor unexpected for Type0 font.", TYPE0FONT_DEBUG_STR);
    font->descriptor = NULL;
  }
}

int
Type0Font_get_wmode (Type0Font *font)
{
  ASSERT(font);

  return font->wmode;
}

#if 0
char *
Type0Font_get_encoding (Type0Font *font)
{
  ASSERT(font);

  return font->encoding;
}
#endif

char *
Type0Font_get_usedchars (Type0Font *font)
{
  ASSERT(font);

  return font->used_chars;
}

pdf_obj *
Type0Font_get_resource (Type0Font *font)
{
  ASSERT(font);

  /*
   * This looks somewhat strange.
   */
  if (!font->indirect) {
    pdf_obj *array;

    array = pdf_new_array();
    pdf_add_array(array, CIDFont_get_resource(font->descendant));
    pdf_add_dict(font->fontdict, pdf_new_name("DescendantFonts"), array);
    font->indirect = pdf_ref_obj(font->fontdict);
  }

  return pdf_link_obj(font->indirect);
}

/******************************** CACHE ********************************/

#define CHECK_ID(n) do {\
  if ((n) < 0 || (n) >= __cache.count)\
    ERROR("%s: Invalid ID %d", TYPE0FONT_DEBUG_STR, (n));\
} while (0)

#define CACHE_ALLOC_SIZE 16u

static struct font_cache {
  int        count;
  int        capacity;
  Type0Font *fonts;
} __cache = {
  0, 0, NULL
};

void
Type0Font_cache_init (void)
{
  if (__cache.fonts)
    ERROR("%s: Already initialized.", TYPE0FONT_DEBUG_STR);
  __cache.count    = 0;
  __cache.capacity = 0;
  __cache.fonts    = NULL;
}

Type0Font *
Type0Font_cache_get (int id)
{
  CHECK_ID(id);

  return &__cache.fonts[id];
}

int
Type0Font_cache_find (const char *map_name, int cmap_id, fontmap_opt *fmap_opt)
{
  int         font_id = -1;
  Type0Font  *font;
  CIDFont    *cidfont;
  CMap       *cmap;
  CIDSysInfo *csi;
  char       *fontname = NULL;
  int         cid_id = -1, parent_id = -1, wmode = 0;

  if (!map_name || cmap_id < 0 || pdf_check_version(1, 2) < 0)
    return -1;

  /*
   * Encoding is Identity-H or Identity-V according as thier WMode value.
   * 
   * We do not use match against the map_name since fonts (TrueType) covers
   * characters across multiple character collection (eg, Adobe-Japan1 and
   * Adobe-Japan2) must be splited into multiple CID-keyed fonts.
   */

  cmap = CMap_cache_get(cmap_id);
  csi  = (CMap_is_Identity(cmap)) ? NULL : CMap_get_CIDSysInfo(cmap) ;

  cid_id = CIDFont_cache_find(map_name, csi, fmap_opt);

  if (cid_id < 0) 
    return -1;

  /*
   * The descendant CID-keyed font has already been registerd.
   * If CID-keyed font with ID = cid_id is new font, then create new parent
   * Type 0 font. Otherwise, there already exists parent Type 0 font and
   * then we find him and return his ID. We must check against their WMode.
   */

  cidfont = CIDFont_cache_get(cid_id);
  wmode   = CMap_get_wmode(cmap);

  /* Does CID-keyed font already have parent ? */
  parent_id = CIDFont_get_parent_id(cidfont, wmode);
  if (parent_id >= 0)
    return parent_id; /* If so, we don't need new one. */

  /*
   * CIDFont does not have parent or his parent's WMode does not matched with
   * wmode. Create new Type0 font.
   */

  if (__cache.count >= __cache.capacity) {
    __cache.capacity += CACHE_ALLOC_SIZE;
    __cache.fonts     = RENEW(__cache.fonts, __cache.capacity, struct Type0Font);
  }
  font_id =  __cache.count;
  font    = &__cache.fonts[font_id];

  Type0Font_init_font_struct(font);

  /*
   * All CJK double-byte characters are mapped so that resulting
   * character codes coincide with CIDs of given character collection.
   * So, the Encoding is always Identity-H for horizontal fonts or
   * Identity-V for vertical fonts.
   */
  if (wmode) {
    font->encoding = NEW(strlen("Identity-V")+1, char);
    strcpy(font->encoding, "Identity-V");
  } else {
    font->encoding = NEW(strlen("Identity-H")+1, char);
    strcpy(font->encoding, "Identity-H");
  }
  font->wmode = wmode;
  font->cmap_id = cmap_id;

  /*
   * Now we start font dictionary.
   */
  font->fontdict = pdf_new_dict();
  pdf_add_dict(font->fontdict, pdf_new_name ("Type"),    pdf_new_name ("Font"));
  pdf_add_dict(font->fontdict, pdf_new_name ("Subtype"), pdf_new_name ("Type0"));

  /*
   * Type0 font does not have FontDescriptor because it is not a simple font.
   * Instead, DescendantFonts appears here.
   *
   * Up to PDF version 1.5, Type0 font must have single descendant font which
   * is a CID-keyed font. Future PDF spec. will allow multiple desecendant
   * fonts.
   */
  font->descendant = cidfont;
  CIDFont_attach_parent(cidfont, font_id, wmode);

  /*
   * PostScript Font name:
   *
   *  Type0 font's fontname is usually descendant CID-keyed font's font name 
   *  appended by -ENCODING.
   */
  fontname = CIDFont_get_fontname(cidfont);

  if (dpx_conf.verbose_level > 0) {
    if (CIDFont_get_embedding(cidfont) && strlen(fontname) > 7)
      MESG("(CID:%s)", fontname+7); /* skip XXXXXX+ */
    else
      MESG("(CID:%s)", fontname);
  }

  /*
   * The difference between CID-keyed font and TrueType font appears here.
   *
   * Glyph substitution for vertical writing is done in CMap mapping process
   * for CID-keyed fonts. But we must rely on OpenType layout table in the
   * case of TrueType fonts. So, we must use different used_chars for each
   * horizontal and vertical fonts in that case.
   *
   * In most PDF file, encoding name is not appended to fontname for Type0
   * fonts having CIDFontType 2 font as their descendant.
   */

  font->used_chars = NULL;
  font->flags      = FLAG_NONE;

  switch (CIDFont_get_subtype(cidfont)) {
  case CIDFONT_TYPE0:
    font->fontname = NEW(strlen(fontname)+strlen(font->encoding)+2, char);
    sprintf(font->fontname, "%s-%s", fontname, font->encoding);
    pdf_add_dict(font->fontdict,
                 pdf_new_name("BaseFont"), pdf_new_name(font->fontname));
    /*
     * Need used_chars to write W, W2.
     */
    if ((parent_id = CIDFont_get_parent_id(cidfont, wmode ? 0 : 1)) < 0) {
      font->used_chars = new_used_chars2();
    } else {
      /* Don't allocate new one. */
      font->used_chars = Type0Font_get_usedchars(Type0Font_cache_get(parent_id));
      font->flags     |= FLAG_USED_CHARS_SHARED;
    }
    break;
  case CIDFONT_TYPE2:
    /*
     * TrueType:
     *
     *  Use different used_chars for H and V.
     */
    pdf_add_dict(font->fontdict,
                 pdf_new_name("BaseFont"), pdf_new_name(fontname));
    font->used_chars = new_used_chars2();
    break;
  default:
    ERROR("Unrecognized CIDFont Type");
    break;
  }

  pdf_add_dict(font->fontdict,
               pdf_new_name("Encoding"), pdf_new_name(font->encoding));

  __cache.count++;

  return font_id;
}

void
Type0Font_cache_close (void)
{
  int   font_id;

  /*
   * This need to be fixed.
   *
   * CIDFont_cache_close() before Type0Font_release because of used_chars.
   * ToUnicode support want descendant CIDFont's CSI and fontname.
   */
  if (__cache.fonts) {
    for (font_id = 0; font_id < __cache.count; font_id++)
      Type0Font_dofont(&__cache.fonts[font_id]);
  }
  CIDFont_cache_close();
  if (__cache.fonts) {
    for (font_id = 0; font_id < __cache.count; font_id++) {
      Type0Font_flush(&__cache.fonts[font_id]);
      Type0Font_clean(&__cache.fonts[font_id]);
    }
    RELEASE(__cache.fonts);
  }
  __cache.fonts    = NULL;
  __cache.count    = 0;
  __cache.capacity = 0;
}

/******************************** COMPAT ********************************/

#ifndef WITHOUT_COMPAT

#include "cmap_read.h"
#include "cmap_write.h"
#include "pdfresource.h"
#include "pdfencoding.h"

static pdf_obj *
create_dummy_CMap (void)
{
  pdf_obj *stream;
  char     buf[32];
  int      i, n;

#define CMAP_PART0 "\
%!PS-Adobe-3.0 Resource-CMap\n\
%%DocumentNeededResources: ProcSet (CIDInit)\n\
%%IncludeResource: ProcSet (CIDInit)\n\
%%BeginResource: CMap (Adobe-Identity-UCS2)\n\
%%Title: (Adobe-Identity-UCS2 Adobe UCS2 0)\n\
%%Version: 1.0\n\
%%Copyright:\n\
%% ---\n\
%%EndComments\n\n\
"
#define CMAP_PART1 "\
/CIDInit /ProcSet findresource begin\n\
\n\
12 dict begin\n\nbegincmap\n\n\
/CIDSystemInfo 3 dict dup begin\n\
  /Registry (Adobe) def\n\
  /Ordering (UCS2) def\n\
  /Supplement 0 def\n\
end def\n\n\
/CMapName /Adobe-Identity-UCS2 def\n\
/CMapVersion 1.0 def\n\
/CMapType 2 def\n\n\
2 begincodespacerange\n\
<0000> <FFFF>\n\
endcodespacerange\n\
"
#define CMAP_PART3 "\
endcmap\n\n\
CMapName currentdict /CMap defineresource pop\n\n\
end\nend\n\n\
%%EndResource\n\
%%EOF\n\
"

  stream = pdf_new_stream(STREAM_COMPRESS);
  pdf_add_stream(stream, CMAP_PART0, strlen(CMAP_PART0));
  pdf_add_stream(stream, CMAP_PART1, strlen(CMAP_PART1));
  pdf_add_stream(stream, "\n100 beginbfrange\n", strlen("\n100 beginbfrange\n"));
  for (i = 0; i < 0x64; i++) {
    n = sprintf(buf,
                "<%02X00> <%02XFF> <%02X00>\n", i, i, i);
    pdf_add_stream(stream, buf, n);
  }
  pdf_add_stream(stream, "endbfrange\n\n", strlen("endbfrange\n\n"));

  pdf_add_stream(stream, "\n100 beginbfrange\n", strlen("\n100 beginbfrange\n"));
  for (i = 0x64; i < 0xc8; i++) {
    n = sprintf(buf,
                "<%02X00> <%02XFF> <%02X00>\n", i, i, i);
    pdf_add_stream(stream, buf, n);
  }
  pdf_add_stream(stream, "endbfrange\n\n", strlen("endbfrange\n\n"));

  pdf_add_stream(stream, "\n48 beginbfrange\n", strlen("\n48 beginbfrange\n"));
  for (i = 0xc8; i <= 0xd7; i++) {
    n = sprintf(buf,
                "<%02X00> <%02XFF> <%02X00>\n", i, i, i);
    pdf_add_stream(stream, buf, n);
  }
  for (i = 0xe0; i <= 0xff; i++) {
    n = sprintf(buf,
                "<%02X00> <%02XFF> <%02X00>\n", i, i, i);
    pdf_add_stream(stream, buf, n);
  }
  pdf_add_stream(stream, "endbfrange\n\n", strlen("endbfrange\n\n"));

  pdf_add_stream(stream, CMAP_PART3, strlen(CMAP_PART3));

  return  stream;
}

static pdf_obj *
pdf_read_ToUnicode_file (const char *cmap_name)
{
  pdf_obj *stream;
  int      res_id = -1;

  ASSERT(cmap_name);

  res_id = pdf_findresource("CMap", cmap_name);
  if (res_id < 0) {
    if (!strcmp(cmap_name, "Adobe-Identity-UCS2"))
      stream = create_dummy_CMap();
    else {
      stream = pdf_load_ToUnicode_stream(cmap_name);
    }
    if (stream) {
      res_id   = pdf_defineresource("CMap",
                                    cmap_name,
                                    stream, PDF_RES_FLUSH_IMMEDIATE);
    }
  }

  return  (res_id < 0 ? NULL : pdf_get_resource_reference(res_id));
}
#endif /* !WITHOUT_COMPAT */
