//	Asuka - VirtualDub Build/Post-Mortem Utility
//	Copyright (C) 2005-2007 Avery Lee
//
//	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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdafx.h>

#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
#include <vd2/system/memory.h>
#include <vd2/system/vectors.h>
#include <vd2/system/vdstl.h>
#include <vd2/system/filesys.h>
#include <vd2/system/math.h>
#include <vd2/Kasumi/pixmaputils.h>
#include <vector>
#include <algorithm>
#include <windows.h>

void VDNORETURN help_fontextract() {
	printf("usage: fontextract <.ttf file> <font name> <start char> <end char> <output file> <symbol prefix>\n");
	exit(5);
}

namespace {
	struct GlyphInfo {
		ABCFLOAT mWidths;
		int mPointStart;
		int mCommandStart;

		GlyphInfo()
			: mPointStart(0)
			, mCommandStart(0)
		{
			mWidths.abcfA = 0;
			mWidths.abcfB = 0;
			mWidths.abcfC = 0;
		}
	};
}

void tool_fontextract(const vdfastvector<const char *>& args, const vdfastvector<const char *>& switches) {
	if (args.size() < 6)
		help_fontextract();

	printf("Asuka: Extracting font: %s -> %s.\n", args[0], args[4]);
	
	int startChar;
	int endChar;

	if (1 != sscanf(args[2], "%d", &startChar) || 1 != sscanf(args[3], "%d", &endChar))
		help_fontextract();

	if (startChar < 0 || endChar > 0xFFFF || endChar <= startChar) {
		printf("Asuka: Invalid character range %d-%d\n", startChar, endChar);
		exit(10);
	}

	int fontsAdded = AddFontResourceEx(args[0], FR_NOT_ENUM | FR_PRIVATE, 0);

	if (!fontsAdded) {
		printf("Asuka: Unable to load font file: %s\n", args[0]);
		exit(10);
	}

	HFONT hfont = CreateFontA(10, 0, 0, 0, 0, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, args[1]);
	if (!hfont) {
		printf("Asuka: Unable to instantiate font: %s\n", args[1]);
		exit(10);
	}

	HDC hdc = CreateDC("DISPLAY", 0, 0, 0);
	HGDIOBJ hfontOld = SelectObject(hdc, hfont);
	MAT2 m = { {0,1},{0,0},{0,0},{0,1} };

	union {
		OUTLINETEXTMETRICW m;
		char buf[2048];
	} metrics;

	if (!GetOutlineTextMetricsW(hdc, sizeof metrics, &metrics.m)) {
		printf("Asuka: Unable to retrieve outline text metrics.\n");
		exit(10);
	}

	SelectObject(hdc, hfontOld);
	DeleteObject(hfont);
	
	hfont = CreateFontA(metrics.m.otmEMSquare, 0, 0, 0, 0, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, args[1]);
	if (!hfont) {
		printf("Asuka: Unable to instantiate font: %s\n", args[1]);
		exit(10);
	}

	hfontOld = SelectObject(hdc, hfont);

	if (!GetOutlineTextMetricsW(hdc, sizeof metrics, &metrics.m)) {
		printf("Asuka: Unable to retrieve outline text metrics.\n");
		exit(10);
	}

	FILE *f = fopen(args[4], "w");
	if (!f) {
		printf("Asuka: Unable to open output file: %s\n", args[4]);
		exit(10);
	}

	std::vector<GlyphInfo> glyphs(endChar - startChar + 1);

	sint32 minX = 0x7FFFFFFF;
	sint32 minY = 0x7FFFFFFF;
	sint32 maxX = -0x7FFFFFFF - 1;
	sint32 maxY = -0x7FFFFFFF - 1;

	vdfastvector<sint32> points;
	vdfastvector<uint8> commands;

	for(int c=startChar; c<endChar; ++c) {
		GlyphInfo& info = glyphs[c - startChar];
		info.mPointStart = points.size() >> 1;
		info.mCommandStart = commands.size();

		GetCharABCWidthsFloatW(hdc, c, c, &info.mWidths);

		// encode glyph outline
		GLYPHMETRICS gm;

		if (GDI_ERROR == GetGlyphOutlineW(hdc, c, GGO_METRICS, &gm, 0, NULL, &m)) {
			printf("Asuka: Unable to obtain glyph metrics for char %d.\n", c);
			exit(20);
		}

		DWORD dwBytes = GetGlyphOutlineW(hdc, c, GGO_NATIVE | GGO_UNHINTED, &gm, 0, NULL, &m);
		if (dwBytes == 0xFFFFFFFF) {
			printf("Asuka: Unable to obtain glyph outline for char %d.\n", c);
			exit(20);
		}

		// GetGlyphOutline() doesn't like providing results for spaces.
		if (gm.gmBlackBoxX <= 0 || gm.gmBlackBoxY <= 0)
			continue;

		void *buf = malloc(dwBytes);
		GetGlyphOutlineW(hdc, c, GGO_NATIVE | GGO_UNHINTED, &gm, dwBytes, buf, &m);

		void *limit = (char *)buf + dwBytes;
		TTPOLYGONHEADER *pHdr = (TTPOLYGONHEADER *)buf;

		sint32 lastCode = 0;

		while(pHdr != limit) {
			VDASSERT(pHdr->dwType == TT_POLYGON_TYPE);

			sint32 x = *(const sint32 *)&pHdr->pfxStart.x;
			sint32 y = *(const sint32 *)&pHdr->pfxStart.y;

			if (minX > x)
				minX = x;
			if (minY > y)
				minY = y;
			if (maxX < x)
				maxX = x;
			if (maxY < y)
				maxY = y;

			points.push_back(x);
			points.push_back(y);

			TTPOLYCURVE *pCurve = (TTPOLYCURVE *)(pHdr + 1);
			TTPOLYCURVE *pCurveEnd = (TTPOLYCURVE *)((char *)pHdr + pHdr->cb);

			while(pCurve != pCurveEnd) {
				VDASSERT(pCurve->wType == TT_PRIM_QSPLINE || pCurve->wType == TT_PRIM_LINE);

				POINTFX *ppfx = pCurve->apfx;

				for(int i=0; i<pCurve->cpfx; ++i) {
					sint32 x = *(const sint32 *)&ppfx->x;
					sint32 y = *(const sint32 *)&ppfx->y;

					if (minX > x)
						minX = x;
					if (minY > y)
						minY = y;
					if (maxX < x)
						maxX = x;
					if (maxY < y)
						maxY = y;

					points.push_back(x);
					points.push_back(y);
					++ppfx;
				}

				if (pCurve->wType == TT_PRIM_LINE) {
					if ((lastCode & 3) != 2) {
						if (lastCode)
							commands.push_back(lastCode);

						lastCode = 2 - 4;
					}

					for(int i=0; i<pCurve->cpfx; ++i) {
						if (lastCode >= 0x7C) {
							commands.push_back(lastCode);
							lastCode = 2 - 4;
						}

						lastCode += 4;
					}
				} else {
					VDASSERT(pCurve->wType == TT_PRIM_QSPLINE);
					VDASSERT(!(pCurve->cpfx % 2));

					if ((lastCode & 3) != 3) {
						if (lastCode)
							commands.push_back(lastCode);

						lastCode = 3 - 4;
					}

					for(int i=0; i<pCurve->cpfx; i+=2) {
						if (lastCode >= 0x7C) {
							commands.push_back(lastCode);
							lastCode = 3 - 4;
						}

						lastCode += 4;
					}
				}

				pCurve = (TTPOLYCURVE *)ppfx;
			}

			if (lastCode) {
				commands.push_back(lastCode | 0x80);
				lastCode = 0;
			}

			vdptrstep(pHdr, pHdr->cb);
		}

		free(buf);
	}

	GlyphInfo& lastInfo = glyphs.back();

	lastInfo.mPointStart = points.size() >> 1;
	lastInfo.mCommandStart = commands.size();

	// write points
	fprintf(f, "// Created by Asuka from %s.  DO NOT EDIT!\n\n", VDFileSplitPath(args[0]));
	fprintf(f, "const uint16 %s_FontPointArray[]={\n", args[5]);

	float scaleX = (maxX > minX) ? 1.0f / (maxX - minX) : 0.0f;
	float scaleY = (maxY > minY) ? 1.0f / (maxY - minY) : 0.0f;

	int pointElementCount = points.size();

	for(int i=0; i<pointElementCount; i+=2) {
		uint8 x = VDClampedRoundFixedToUint8Fast((float)(points[i+0] - minX) * scaleX);
		uint8 y = VDClampedRoundFixedToUint8Fast((float)(points[i+1] - minY) * scaleY);
		uint16 pt = ((uint16)y << 8) + x;

		fprintf(f, "0x%04x,", pt);

		if ((i & 30) == 30)
			putc('\n', f);
	}

	if (pointElementCount & 30)
		putc('\n', f);

	fprintf(f, "};\n\n");

	// write commands
	fprintf(f, "const uint8 %s_FontCommandArray[]={\n", args[5]);

	int commandElementCount = commands.size();

	for(int i=0; i<commandElementCount; ++i) {
		fprintf(f, "0x%02x,", commands[i]);

		if ((i & 15) == 15)
			putc('\n', f);
	}

	if (commandElementCount & 15)
		putc('\n', f);

	fprintf(f, "};\n\n");

	// glyph data structures
	fprintf(f, "const VDOutlineFontGlyphInfo %s_FontGlyphArray[]={\n", args[5]);
	for(int i=startChar; i<=endChar; ++i) {
		const GlyphInfo& info = glyphs[i - startChar];
		fprintf(f, "{ %d, %d, %d, %d, %d },\n", info.mPointStart, info.mCommandStart, VDRoundToInt(info.mWidths.abcfA), VDRoundToInt(info.mWidths.abcfB), VDRoundToInt(info.mWidths.abcfC));
	}
	fprintf(f, "};\n\n");

	// top-level data structure
	fprintf(f, "const VDOutlineFontInfo %s_FontInfo={\n", args[5]);
	fprintf(f, "\t%s_FontPointArray,\n", args[5]);
	fprintf(f, "\t%s_FontCommandArray,\n", args[5]);
	fprintf(f, "\t%s_FontGlyphArray,\n", args[5]);
	fprintf(f, "\t%d, %d,\n", startChar, endChar);
	fprintf(f, "\t%d, %d, %d, %d,\t// bounds (16:16)\n", minX, minY, maxX, maxY);
	fprintf(f, "\t%d,\t// em square\n", metrics.m.otmEMSquare);
	fprintf(f, "\t%d,\t// ascent\n", metrics.m.otmAscent);
	fprintf(f, "\t%d,\t// descent\n", metrics.m.otmDescent);
	fprintf(f, "\t%d,\t// line gap\n", metrics.m.otmLineGap);
	fprintf(f, "};\n");

	printf("Asuka: %d point bytes, %d command bytes, %d glyph info bytes.\n", (int)points.size(), (int)commands.size(), (endChar - startChar + 1) * 10);

	SelectObject(hdc, hfontOld);
	DeleteDC(hdc);

	DeleteObject(hfont);
}
