/*************************************************************************
** FontWriter.cpp                                                       **
**                                                                      **
** This file is part of dvisvgm -- a fast DVI to SVG converter          **
** Copyright (C) 2005-2022 Martin Gieseking <martin.gieseking@uos.de>   **
**                                                                      **
** 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 3 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, see <http://www.gnu.org/licenses/>. **
*************************************************************************/

#include <algorithm>
#include <array>
#include "FontWriter.hpp"
#include "Message.hpp"
#include "utility.hpp"

using namespace std;

bool FontWriter::AUTOHINT_FONTS = false;

const array<FontWriter::FontFormatInfo, 4> FontWriter::_formatInfos {{
	{FontWriter::FontFormat::SVG, "image/svg+xml", "svg", "svg"},
	{FontWriter::FontFormat::TTF, "application/x-font-ttf", "ttf", "truetype"},
	{FontWriter::FontFormat::WOFF, "application/x-font-woff", "woff", "woff"},
	{FontWriter::FontFormat::WOFF2, "application/x-font-woff2", "woff2", "woff2"},
}};


/** Returns the corresponding FontFormat for a given format name (e.g. "svg", "woff" etc.). */
FontWriter::FontFormat FontWriter::toFontFormat (string formatstr) {
	formatstr = util::tolower(formatstr);
	auto it = find_if(_formatInfos.begin(), _formatInfos.end(), [&](const FontFormatInfo &info) {
		return info.formatstr_short == formatstr;
	});
	return it != _formatInfos.end() ? it->format : FontFormat::UNKNOWN;
}


/** Returns the corresponding FontFormatInfo for a given FontFormat. */
const FontWriter::FontFormatInfo* FontWriter::fontFormatInfo (FontFormat format) {
	auto it = find_if(_formatInfos.begin(), _formatInfos.end(), [&](const FontFormatInfo &info) {
		return info.format == format;
	});
	return it != _formatInfos.end() ? &(*it) : nullptr;
}


/** Returns the names of all supported font formats. */
vector<string> FontWriter::supportedFormats () {
	vector<string> formats;
	for (const FontFormatInfo &info : _formatInfos)
		formats.push_back(info.formatstr_short);
	return formats;
}


#include <config.h>

#ifdef DISABLE_WOFF
// dummy functions used if WOFF support is disabled
FontWriter::FontWriter (const PhysicalFont &font) : _font(font) {}
std::string FontWriter::createFontFile (FontFormat format, const set<int> &charcodes, GFGlyphTracer::Callback *cb) const {return "";}
bool FontWriter::writeCSSFontFace (FontFormat format, const set<int> &charcodes, ostream &os, GFGlyphTracer::Callback *cb) const {return false;}
#else
#include <cmath>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <woff2/encode.h>
#include "ffwrapper.h"
#include "Bezier.hpp"
#include "FileSystem.hpp"
#include "Font.hpp"
#include "Glyph.hpp"
#include "TTFAutohint.hpp"
#include "TrueTypeFont.hpp"


FontWriter::FontWriter (const PhysicalFont &font) : _font(font) {
	static bool initialized=false;
	if (!initialized) {
		ff_init();
		initialized = true;
	}
}


struct SFDActions : Glyph::IterationActions {
	explicit SFDActions (ostream &os) : _os(os) {}

	using Point = Glyph::Point;
	void moveto (const Point &p) override {write('m', p);}
	void lineto (const Point &p) override {write('l', p);}
	void cubicto (const Point &p1, const Point &p2, const Point &p3) override {write('c', p1, p2, p3);	}
	void closepath () override {write('m', startPoint());}

	void quadto (const Point &p1, const Point &p2) override {
		// convert quadratic Bézier curve to cubic one
		DPair pt0(currentPoint().x(), currentPoint().y());
		DPair pt1(p1.x(), p1.y());
		DPair pt2(p2.x(), p2.y());
		Bezier b(pt0, pt1, pt2);
		write('c', round(b.point(0)), round(b.point(1)), round(b.point(2)), round(b.point(3)));
	}

	template <typename ...Args>
	void write (char cmd, const Args& ...args) {
		writeParams(args...);
		_os << cmd << " 0\n";
	}

	static void writeParams () {}

	template <typename Pt, typename ...Args>
	void writeParams (const Pt &p, const Args& ...args) const {
		_os << p.x() << ' ' << p.y() << ' ';
		writeParams(args...);
	}

	ostream &_os;
   Glyph::Point _startPoint, _currentPoint;
};


/** Creates a Spline Font Database (SFD) file describing the font and its glyphs.
 *  https://fontforge.github.io/sfdformat.html */
static void writeSFD (const string &sfdname, const PhysicalFont &font, const set<int> &charcodes, GFGlyphTracer::Callback *cb) {
	ofstream sfd(sfdname);
	if (!sfd)
		throw FontWriterException("failed writing SFD file "+sfdname);

	sfd <<
		"SplineFontDB: 3.0\n"
		"FontName: " << font.name() << '\n';

	// ensure that the sum of the SFD's Ascent and Descent values equals the font's units per EM
	double yext = font.ascent()+font.descent();
	double scale = double(font.unitsPerEm())/(yext != 0 ? yext : abs(font.ascent()));
	sfd <<
		"Ascent: " << font.ascent()*scale << "\n"
		"Descent: " << font.descent()*scale << "\n"
		"LayerCount: 2\n"           // number of layers must be 2 at least
		"Layer: 0 0 \"Back\" 1\n"   // layer 0: background layer with cubic splines
		"Layer: 1 0 \"Fore\" 0\n"   // layer 1: foreground layer with cubic splines
		"Encoding: UnicodeFull\n"   // character codes can use the full Unicode range
		"BeginChars: 1114112 " << charcodes.size() << '\n';

	double extend = font.style() ? font.style()->extend : 1;
	for (int c : charcodes) {
		string name = font.glyphName(c);
		if (name.empty()) {
			// if the font doesn't provide glyph names, use AGL name uFOO
			ostringstream oss;
			oss << 'u' << hex << uppercase << setw(4) << setfill('0') << c;
			name = oss.str();
		}
		uint32_t codepoint = font.unicode(c);
		sfd <<
			"StartChar: " << name << "\n"
			"Encoding: "  << codepoint << ' ' << codepoint << " 0\n"
			"Width: "     << font.hAdvance(c)*extend << "\n"
			"VWidth: "    << font.vAdvance(c) << "\n"
			"Fore\n"
			"SplineSet\n";
		Glyph glyph;
		if (font.getGlyph(c, glyph, cb)) {
			SFDActions actions(sfd);
			glyph.iterate(actions, false);
		}
		sfd <<
			"EndSplineSet\n"
			"EndChar\n";
	}
	sfd.flush();
	sfd.close();
	if (sfd.fail())
		throw FontWriterException("failed writing SFD file "+sfdname);
}


bool FontWriter::createTTFFile (const string &sfdname, const string &ttfname) const {
	TTFAutohint autohinter;
	if (!autohinter.available())
		return ff_sfd_to_ttf(sfdname.c_str(), ttfname.c_str(), AUTOHINT_FONTS);

	bool ok = ff_sfd_to_ttf(sfdname.c_str(), ttfname.c_str(), false);
	if (ok && AUTOHINT_FONTS) {
		string tmpname = ttfname+"-ah";
		int errnum = autohinter.autohint(ttfname, tmpname, true);
		if (errnum) {
			Message::wstream(true) << "failed to autohint font '" << _font.name() << "'";
			string msg = autohinter.lastErrorMessage();
			if (!msg.empty())
				Message::wstream() << " (" << msg << ")";
			// keep the unhinted TTF
			FileSystem::remove(tmpname);
		}
		else {
			FileSystem::remove(ttfname);
			FileSystem::rename(tmpname, ttfname);
		}
	}
	return ok;
}


/** Creates a font file containing a given set of glyphs mapped to their Unicode points.
 * @param[in] format target font format
 * @param[in] charcodes character codes of the glyphs to be considered
 * @param[in] cb callback object that allows to react to events triggered by the glyph tracer
 * @return name of the created font file */
string FontWriter::createFontFile (FontFormat format, const set<int> &charcodes, GFGlyphTracer::Callback *cb) const {
	string tmpdir = FileSystem::tmpdir();
	string basename = tmpdir+_font.name()+"-tmp";
	string sfdname = basename+".sfd";
	writeSFD(sfdname, _font, charcodes, cb);
	string ttfname = basename+".ttf";
	string targetname = basename+"."+fontFormatInfo(format)->formatstr_short;
	bool ok = createTTFFile(sfdname, ttfname);
	if (ok) {
		if (format == FontFormat::WOFF || format == FontFormat::WOFF2) {
			TrueTypeFont ttf(ttfname);
			if (format == FontFormat::WOFF)
				ttf.writeWOFF(targetname);
			else
				ok = ttf.writeWOFF2(targetname);
			if (!PhysicalFont::KEEP_TEMP_FILES)
				FileSystem::remove(ttfname);
		}
	}
	if (!PhysicalFont::KEEP_TEMP_FILES)
		FileSystem::remove(sfdname);
	if (!ok)
		throw FontWriterException("failed writing "+string(fontFormatInfo(format)->formatstr_short)+ " file " + targetname);
	return targetname;
}


/** Writes a CSS font-face rule to an output stream that references or contains the WOFF/TTF font data.
 * @param[in] format target font format
 * @param[in] charcodes character codes of the glyphs to be considered
 * @param[in] os stream the CSS data is written to
 * @param[in] cb callback object that allows to react to events triggered by the glyph tracer
 * @return true on success */
bool FontWriter::writeCSSFontFace (FontFormat format, const set<int> &charcodes, ostream &os, GFGlyphTracer::Callback *cb) const {
	if (const FontFormatInfo *info = fontFormatInfo(format)) {
		string filename = createFontFile(format, charcodes, cb);
		ifstream ifs(filename, ios::binary);
		if (ifs) {
			os << "@font-face{"
				<< "font-family:" << _font.name() << ';'
				<< "src:url(data:" << info->mimetype << ";base64,";
			util::base64_copy(ifs, os);
			os << ") format('" << info->formatstr_long << "');}\n";
			ifs.close();
			if (!PhysicalFont::KEEP_TEMP_FILES)
				FileSystem::remove(filename);
			return true;
		}
	}
	return false;
}

#endif // !DISABLE_WOFF
