/* secondary.{cc,hh} -- code for generating fake glyphs
 *
 * Copyright (c) 2003-2006 Eddie Kohler
 *
 * 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.
 */

#include <config.h>
#include "secondary.hh"
#include "metrics.hh"
#include "automatic.hh"
#include "otftotfm.hh"
#include "util.hh"
#include <efont/t1bounds.hh>
#include <efont/t1font.hh>
#include <efont/t1rw.hh>
#include <efont/otfname.hh>
#include <efont/otfos2.hh>
#include <efont/otfpost.hh>
#include <efont/t1csgen.hh>
#include <efont/t1unparser.hh>
#include <efont/ttfcs.hh>
#include <lcdf/straccum.hh>
#include <stdarg.h>
#include <errno.h>
#include <limits.h>
#include <algorithm>

enum { U_EXCLAMDOWN = 0x00A1,	// U+00A1 INVERTED EXCLAMATION MARK
       U_DEGREE = 0x00B0,	// U+00B0 DEGREE SIGN
       U_QUESTIONDOWN = 0x00BF,	// U+00BF INVERTED QUESTION MARK
       U_IJ = 0x0132,		// U+0132 LATIN CAPITAL LIGATURE IJ
       U_ij = 0x0133,		// U+0133 LATIN SMALL LIGATURE IJ
       U_DOTLESSJ = 0x0237,	// U+0237 LATIN SMALL LETTER DOTLESS J
       U_RINGABOVE = 0x02DA,	// U+02DA RING ABOVE
       U_COMBININGRINGABOVE = 0x030A,	// U+030A COMBINING RING ABOVE
       U_CWM = 0x200C,		// U+200C ZERO WIDTH NON-JOINER
       U_ENDASH = 0x2013,	// U+2013 EN DASH
       U_PERTENTHOUSAND = 0x2031, // U+2031 PER TEN THOUSAND SIGN
       U_INTERROBANG = 0x203D,	// U+203D INTERROBANG
       U_FRACTION = 0x2044,	// U+2044 FRACTION SLASH
       U_CENTIGRADE = 0x2103,	// U+2103 DEGREE CELSIUS
       U_ASTERISKMATH = 0x2217,	// U+2217 ASTERISK OPERATOR
       U_BARDBL = 0x2225,	// U+2225 PARALLEL TO
       U_VISIBLESPACE = 0x2423,	// U+2423 OPEN BOX
       U_DBLBRACKETLEFT = 0x27E6,
       U_DBLBRACKETRIGHT = 0x27E7,
       U_SS = 0xD800,		// invalid Unicode
       U_EMPTYSLOT = 0xD801,	// invalid Unicode (not handled by Secondary)
       U_ALTSELECTOR = 0xD802,	// invalid Unicode
       U_SSSMALL = 0xD803,	// invalid Unicode
       U_FFSMALL = 0xD804,	// invalid Unicode
       U_FISMALL = 0xD805,	// invalid Unicode
       U_FLSMALL = 0xD806,	// invalid Unicode
       U_FFISMALL = 0xD807,	// invalid Unicode
       U_FFLSMALL = 0xD808,	// invalid Unicode
       U_CAPITALCWM = 0xD809,	// invalid Unicode
       U_ASCENDERCWM = 0xD80A,	// invalid Unicode
       U_INTERROBANGDOWN = 0xD80B, // invalid Unicode
       U_TWELVEUDASH = 0xD80C,	// invalid Unicode
       U_RINGFITTED = 0xD80D,	// invalid Unicode
       U_VS1 = 0xFE00,
       U_VS16 = 0xFE0F,
       U_VS17 = 0xE0100,
       U_VS256 = 0xE01FF,
       U_DOTLESSJ_2 = 0xF6BE,
       U_THREEQUARTERSEMDASH = 0xF6DE,
       U_FSMALL = 0xF766,
       U_ISMALL = 0xF769,
       U_LSMALL = 0xF76C,
       U_SSMALL = 0xF773,
       U_MATHDOTLESSJ = 0x1D6A5
};


FontInfo::FontInfo(const Efont::OpenType::Font *otf_, ErrorHandler *errh)
    : otf(otf_), cmap(0), cff_file(0), cff(0), post(0), name(0), _nglyphs(-1),
      _got_glyph_names(false), _ttb_program(0)
{
    cmap = new Efont::OpenType::Cmap(otf->table("cmap"), errh);
    assert(cmap->ok());

    if (String cff_string = otf->table("CFF")) {
	cff_file = new Efont::Cff(cff_string, errh);
	if (!cff_file->ok())
	    return;
	Efont::Cff::FontParent *fp = cff_file->font(PermString(), errh);
	if (!fp || !fp->ok())
	    return;
	if (!(cff = dynamic_cast<Efont::Cff::Font *>(fp))) {
	    errh->error("CID-keyed fonts not supported");
	    return;
	}
	_nglyphs = cff->nglyphs();
    }

    if (!cff) {
	post = new Efont::OpenType::Post(otf->table("post"), errh);
	// read number of glyphs from 'maxp' -- should probably be elsewhere
	if (Efont::OpenType::Data maxp = otf->table("maxp"))
	    if (maxp.length() >= 6)
		_nglyphs = maxp.u16(4);
	if (_nglyphs < 0 && post->ok())
	    _nglyphs = post->nglyphs();
    }
    
    name = new Efont::OpenType::Name(otf->table("name"), errh);
}

FontInfo::~FontInfo()
{
    delete cmap;
    delete cff_file;
    delete cff;
    delete post;
    delete name;
    delete _ttb_program;
}

bool
FontInfo::ok() const
{
    if (cff)
	return cmap->ok() && cff->ok();
    else
	return post && post->ok() && name && name->ok();
}

bool
FontInfo::glyph_names(Vector<PermString> &glyph_names) const
{
    program()->glyph_names(glyph_names);
    return true;
}

int
FontInfo::glyphid(PermString name) const
{
    if (cff)
	return cff->glyphid(name);
    else {
	if (!_got_glyph_names) {
	    glyph_names(_glyph_names);
	    _got_glyph_names = true;
	}
	PermString *found = std::find(_glyph_names.begin(), _glyph_names.end(), name);
	if (found == _glyph_names.end())
	    return 0;
	return found - _glyph_names.begin();
    }
}

String
FontInfo::family_name() const
{
    if (cff)
	return cff->dict_string(Efont::Cff::oFamilyName);
    else
	return name->english_name(Efont::OpenType::Name::N_FAMILY);
}

String
FontInfo::postscript_name() const
{
    if (cff)
	return cff->font_name();
    else
	return name->english_name(Efont::OpenType::Name::N_POSTSCRIPT);
}

const Efont::CharstringProgram *
FontInfo::program() const
{
    if (cff)
	return cff;
    else {
	if (!_ttb_program)
	    _ttb_program = new Efont::TrueTypeBoundsCharstringProgram(otf);
	return _ttb_program;
    }
}

bool
FontInfo::is_fixed_pitch() const
{
    if (cff) {
	double d;
	return (cff->dict_value(Efont::Cff::oIsFixedPitch, &d) && d);
    } else
	return post->is_fixed_pitch();
}

double
FontInfo::italic_angle() const
{
    if (cff) {
	double d;
	(void) cff->dict_value(Efont::Cff::oItalicAngle, &d);
	return d;
    } else
	return post->italic_angle();
}


/* */

Secondary::~Secondary()
{
}

bool
Secondary::encode_uni(int code, PermString name, uint32_t uni, Metrics &metrics, ErrorHandler *errh)
{
    Vector<Setting> v;
    if (setting(uni, v, metrics, errh)) {
	metrics.encode_virtual(code, name, uni, v);
	return true;
    } else if (_next)
	return _next->encode_uni(code, name, uni, metrics, errh);
    else
	return false;
}

T1Secondary::T1Secondary(const FontInfo &finfo, const String &font_name,
			 const String &otf_file_name)
    : _finfo(finfo), _font_name(font_name), _otf_file_name(otf_file_name),
      _xheight(font_x_height(finfo, Transform())), _spacewidth(1000)
{
    int bounds[4], width;
    if (char_bounds(bounds, width, finfo, Transform(), ' '))
	_spacewidth = width;
}

bool
Secondary::setting(uint32_t uni, Vector<Setting> &v, Metrics &metrics, ErrorHandler *errh)
{
    if (_next)
	return _next->setting(uni, v, metrics, errh);
    else
	return false;
}

bool
T1Secondary::char_setting(Vector<Setting> &v, Metrics &metrics, int uni, ...)
{
    Vector<int> codes;

    // collect codes
    va_list val;
    va_start(val, uni);
    for (; uni; uni = va_arg(val, int)) {
	int code = metrics.unicode_encoding(uni);
	if (code < 0) {
	    Glyph glyph = _finfo.cmap->map_uni(uni);
	    if (glyph == 0 || (code = metrics.force_encoding(glyph)) < 0)
		return false;
	}
	codes.push_back(code);
    }
    va_end(val);

    // generate setting
    for (int i = 0; i < codes.size(); i++) {
	if (i)
	    v.push_back(Setting(Setting::KERN));
	v.push_back(Setting(Setting::SHOW, codes[i], metrics.base_glyph(codes[i])));
    }
    return true;
}

bool
T1Secondary::encode_uni(int code, PermString name, uint32_t uni, Metrics &metrics, ErrorHandler *errh)
{
    if (uni == U_ALTSELECTOR || (uni >= U_VS1 && uni <= U_VS16) || (uni >= U_VS17 && uni <= U_VS256)) {
	Vector<Setting> v;
	setting(uni, v, metrics, errh);
	int which = (uni == U_ALTSELECTOR ? 0 : (uni <= U_VS16 ? uni - U_VS1 + 1 : uni - U_VS17 + 17));
	metrics.encode_virtual(code, (which ? permprintf("<vs%d>", which) : PermString("<altselector>")), uni, v);
	metrics.add_altselector_code(code, which);
	return true;
    } else
	return Secondary::encode_uni(code, name, uni, metrics, errh);
}


static String dotlessj_file_name;

static String
dotlessj_dvips_include(const String &, const FontInfo &, ErrorHandler *)
{
    return "<" + pathname_filename(dotlessj_file_name);
}

int
T1Secondary::dotlessj_font(Metrics &metrics, ErrorHandler *errh, Glyph &dj_glyph)
{
    if (!_font_name || !_finfo.otf || !_finfo.cff)
	return -1;
    
    String dj_name = suffix_font_name(_font_name, "--lcdfj");
    for (int i = 0; i < metrics.n_mapped_fonts(); i++)
	if (metrics.mapped_font_name(i) == dj_name)
	    return i;
    
    if (String filename = installed_type1_dotlessj(_otf_file_name, _finfo.cff->font_name(), (output_flags & G_DOTLESSJ), errh)) {

	// check for special case: "\0" means the font's "j" is already
	// dotless
	if (filename == String("\0", 1))
	    return J_NODOT;
	
	// open dotless-j font file
	FILE *f = fopen(filename.c_str(), "rb");
	if (!f) {
	    errh->error("%s: %s", filename.c_str(), strerror(errno));
	    return -1;
	}

	// read font
	Efont::Type1Reader *reader;
	int c = getc(f);
	ungetc(c, f);
	if (c == 128)
	    reader = new Efont::Type1PFBReader(f);
	else
	    reader = new Efont::Type1PFAReader(f);
  	Efont::Type1Font *font = new Efont::Type1Font(*reader);
	delete reader;
	
	if (!font->ok()) {
	    errh->error("%s: no glyphs in dotless-J font", filename.c_str());
	    delete font;
	    return -1;
	}

	// create metrics for dotless-J
	Metrics dj_metrics(font, 256);
	Vector<PermString> glyph_names;
	font->glyph_names(glyph_names);
	Vector<PermString>::iterator g = std::find(glyph_names.begin(), glyph_names.end(), "uni0237");
	if (g != glyph_names.end()) {
	    dj_glyph = g - glyph_names.begin();
	    dj_metrics.encode('j', U_DOTLESSJ, dj_glyph);
	} else {
	    errh->error("%s: dotless-J font has no 'uni0237' glyph", filename.c_str());
	    delete font;
	    return -1;
	}
	::dotlessj_file_name = filename;
	output_metrics(dj_metrics, font->font_name(), -1, _finfo, String(), String(), dj_name, dotlessj_dvips_include, errh);
	
	// add font to metrics
	return metrics.add_mapped_font(font, dj_name);
	
    } else
	return -1;
}

bool
T1Secondary::setting(uint32_t uni, Vector<Setting> &v, Metrics &metrics, ErrorHandler *errh)
{
    Transform xform;
    int vsize = v.size();
    
    switch (uni) {
	
      case U_CWM:
      case U_ALTSELECTOR:
	v.push_back(Setting(Setting::RULE, 0, _xheight));
	return true;

      case U_CAPITALCWM:
	v.push_back(Setting(Setting::RULE, 0, font_cap_height(_finfo, xform)));
	return true;

      case U_ASCENDERCWM:
	v.push_back(Setting(Setting::RULE, 0, font_ascender(_finfo, xform)));
	return true;

      case U_VISIBLESPACE:
	v.push_back(Setting(Setting::MOVE, 50, -150));
	v.push_back(Setting(Setting::RULE, 40, 150));
	v.push_back(Setting(Setting::RULE, _spacewidth, 40));
	v.push_back(Setting(Setting::RULE, 40, 150));
	v.push_back(Setting(Setting::MOVE, 50, 150));
	return true;

      case U_SS:
	if (char_setting(v, metrics, 'S', 'S', 0))
	    return true;
	break;

      case U_SSSMALL:
	if (char_setting(v, metrics, U_SSMALL, U_SSMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 's', 's', 0))
	    return true;
	break;

      case U_FFSMALL:
	if (char_setting(v, metrics, U_FSMALL, U_FSMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 'f', 'f', 0))
	    return true;
	break;

      case U_FISMALL:
	if (char_setting(v, metrics, U_FSMALL, U_ISMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 'f', 'i', 0))
	    return true;
	break;

      case U_FLSMALL:
	if (char_setting(v, metrics, U_FSMALL, U_LSMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 'f', 'l', 0))
	    return true;
	break;

      case U_FFISMALL:
	if (char_setting(v, metrics, U_FSMALL, U_FSMALL, U_ISMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 'f', 'f', 'i', 0))
	    return true;
	break;

      case U_FFLSMALL:
	if (char_setting(v, metrics, U_FSMALL, U_FSMALL, U_LSMALL, 0))
	    return true;
	else if (char_setting(v, metrics, 'f', 'f', 'l', 0))
	    return true;
	break;

      case U_IJ:
	if (char_setting(v, metrics, 'I', 'J', 0))
	    return true;
	break;

      case U_ij:
	if (char_setting(v, metrics, 'i', 'j', 0))
	    return true;
	break;

      case U_DOTLESSJ:
      case U_DOTLESSJ_2:
      case U_MATHDOTLESSJ: {
	  Glyph dj_glyph;
	  int which = dotlessj_font(metrics, errh, dj_glyph);
	  if (which >= 0) {
	      v.push_back(Setting(Setting::FONT, which));
	      v.push_back(Setting(Setting::SHOW, 'j', dj_glyph));
	      return true;
	  } else if (which == J_NODOT && char_setting(v, metrics, 'j', 0))
	      return true;
	  break;
      }

      case U_DBLBRACKETLEFT:
	if (char_setting(v, metrics, '[', 0)) {
	    if (!_finfo.is_fixed_pitch()) {
		double d = char_one_bound(_finfo, xform, 4, true, 0, '[', 0);
		v.push_back(Setting(Setting::MOVE, (int) (-0.666 * d), 0));
	    }
	    char_setting(v, metrics, '[', 0);
	    return true;
	}
	break;

      case U_DBLBRACKETRIGHT:
	if (char_setting(v, metrics, ']', 0)) {
	    if (!_finfo.is_fixed_pitch()) {
		double d = char_one_bound(_finfo, xform, 4, true, 0, ']', 0);
		v.push_back(Setting(Setting::MOVE, (int) (-0.666 * d), 0));
	    }
	    char_setting(v, metrics, ']', 0);
	    return true;
	}
	break;
	
      case U_BARDBL:
	if (char_setting(v, metrics, '|', 0)) {
	    if (!_finfo.is_fixed_pitch()) {
		double d = char_one_bound(_finfo, Transform(), 4, true, 0, '|', 0);
		v.push_back(Setting(Setting::MOVE, (int) (-0.333 * d), 0));
	    }
	    char_setting(v, metrics, '|', 0);
	    return true;
	}
	break;

      case U_ASTERISKMATH: {
	  int bounds[5];
	  double dropdown = 0;
	  if (char_bounds(bounds, bounds[4], _finfo, xform, '*'))
	      dropdown += std::max(bounds[3], 0) + std::min(bounds[1], 0);
	  if (char_bounds(bounds, bounds[4], _finfo, xform, '('))
	      dropdown -= std::max(bounds[3], 0) + std::min(bounds[1], 0);
	  v.push_back(Setting(Setting::MOVE, 0, (int) (-dropdown / 2)));
	  if (char_setting(v, metrics, '*', 0))
	      return true;
	  break;
      }

      case U_TWELVEUDASH:
	if (char_setting(v, metrics, U_ENDASH, 0)) {
	    if (!_finfo.is_fixed_pitch()) {
		double d = char_one_bound(_finfo, xform, 4, true, 0, U_ENDASH, 0);
		v.push_back(Setting(Setting::MOVE, (int) (667 - 2 * d), 0));
	    }
	    char_setting(v, metrics, U_ENDASH, 0);
	    return true;
	}
	break;
	
      case U_THREEQUARTERSEMDASH:
	if (char_setting(v, metrics, U_ENDASH, 0)) {
	    if (!_finfo.is_fixed_pitch()) {
		double d = char_one_bound(_finfo, xform, 4, true, 0, U_ENDASH, 0);
		v.push_back(Setting(Setting::MOVE, (int) (750 - 2 * d), 0));
	    }
	    char_setting(v, metrics, U_ENDASH, 0);
	    return true;
	}
	break;
	
      case U_CENTIGRADE:
        // TODO: set italic correction to that of a 'C'
	if (char_setting(v, metrics, U_DEGREE, 'C', 0))
	    return true;
	break;
	
      case U_INTERROBANG: {
	  double exclam_offset =
	      (char_one_bound(_finfo, xform, 4, true, 0, '?', 0)
	       - char_one_bound(_finfo, xform, 4, true, 0, '!', 0)) * 0.5 + 50;
	  v.push_back(Setting(Setting::PUSH));
	  v.push_back(Setting(Setting::MOVE, (int) exclam_offset, 0));
	  if (char_setting(v, metrics, '!', 0)) {
	      v.push_back(Setting(Setting::POP));
	      if (char_setting(v, metrics, '?', 0))
		  return true;
	  }
	  break;
      }

      case U_INTERROBANGDOWN: {
	  double exclam_offset =
	      (char_one_bound(_finfo, xform, 4, true, 0, U_QUESTIONDOWN, 0)
	       - char_one_bound(_finfo, xform, 4, true, 0, U_EXCLAMDOWN, 0)) * 0.5 + 50;
	  v.push_back(Setting(Setting::PUSH));
	  v.push_back(Setting(Setting::MOVE, (int) exclam_offset, 0));
	  if (char_setting(v, metrics, U_EXCLAMDOWN, 0)) {
	      v.push_back(Setting(Setting::POP));
	      if (char_setting(v, metrics, U_QUESTIONDOWN, 0))
		  return true;
	  }
	  break;
      }

      case U_PERTENTHOUSAND:
	if (char_setting(v, metrics, 0xF661, U_FRACTION, 0xF655, 0xF655, 0xF655, 0))
	    return true;
	break;

      case U_RINGFITTED: {
	  int A_width = char_one_bound(_finfo, xform, 4, true, -1000, 'A', 0);
	  uint32_t ring_char = U_RINGABOVE;
	  int ring_width = char_one_bound(_finfo, xform, 4, true, -1000, ring_char, 0);
	  if (ring_width <= -1000) {
	      ring_char = U_COMBININGRINGABOVE;
	      ring_width = char_one_bound(_finfo, xform, 4, true, -1000, ring_char, 0);
	  }
	  if (A_width > -1000 && ring_width > -1000) {
	      int offset = (A_width - ring_width) / 2;
	      v.push_back(Setting(Setting::MOVE, offset, 0));
	      if (char_setting(v, metrics, ring_char, 0)) {
		  v.push_back(Setting(Setting::MOVE, A_width - ring_width - offset, 0));
		  return true;
	      }
	  }
	  break;
      }
	
    }

    // didn't find a good setting, restore v to pristine state
    while (v.size() > vsize)
	v.pop_back();

    // variant selectors get the same setting as ALTSELECTOR
    if ((uni >= U_VS1 && uni <= U_VS16) || (uni >= U_VS17 && uni <= U_VS256))
	return setting(U_ALTSELECTOR, v, metrics, errh);

    // otherwise, try other secondaries
    return Secondary::setting(uni, v, metrics, errh);
}


bool
char_bounds(int bounds[4], int &width, const FontInfo &finfo,
	    const Transform &transform, uint32_t uni)
{
    if (Efont::OpenType::Glyph g = finfo.cmap->map_uni(uni))
	return Efont::CharstringBounds::bounds(transform, finfo.program()->glyph_context(g), bounds, width);
    else
	return false;
}

int
char_one_bound(const FontInfo &finfo, const Transform &transform,
	       int dimen, bool max, int best, uint32_t uni, ...)
{
    int bounds[5];
    va_list val;
    va_start(val, uni);
    while (uni != 0) {
	if (char_bounds(bounds, bounds[4], finfo, transform, uni))
	    if (max ? bounds[dimen] > best : bounds[dimen] < best)
		best = bounds[dimen];
	uni = va_arg(val, uint32_t);
    }
    va_end(val);
    return best;
}

int
font_x_height(const FontInfo &finfo, const Transform &font_xform)
{
    try {
	Efont::OpenType::Os2 os2(finfo.otf->table("OS/2"));
	return os2.x_height();
    } catch (Efont::OpenType::Bounds) {
	// XXX what if 'x', 'm', 'z' were subject to substitution?
	return char_one_bound(finfo, font_xform, 3, false, 1000,
			      'x', 'm', 'z', 0);
    }
}

int
font_cap_height(const FontInfo &finfo, const Transform &font_xform)
{
    try {
	Efont::OpenType::Os2 os2(finfo.otf->table("OS/2"));
	return os2.cap_height();
    } catch (Efont::OpenType::Bounds) {
	// XXX what if 'H', 'O', 'B' were subject to substitution?
	return char_one_bound(finfo, font_xform, 3, false, 1000,
			      'H', 'O', 'B', 0);
    }
}

int
font_ascender(const FontInfo &finfo, const Transform &font_xform)
{
    try {
	Efont::OpenType::Os2 os2(finfo.otf->table("OS/2"));
	return os2.typo_ascender();
    } catch (Efont::OpenType::Bounds) {
	// XXX what if 'd', 'l' were subject to substitution?
	return char_one_bound(finfo, font_xform, 3, true,
			      font_x_height(finfo, font_xform),
			      'd', 'l', 0);
    }
}
