Beispiel #1
0
DECLARE_PALETTED(Pal4, Any24) {
	const uint8 *src = (const uint8 *)src0;
	uint8 *dst = (uint8 *)dst0;
	const uint8 *pal = (const uint8 *)pal0;

	src += (w-1) >> 1;
	dst += ((w-1) & ~1) * 3;

	srcpitch += (w+1) >> 1;
	dstpitch += ((w+1) & ~1) * 3;

	do {
		int wt = w;

		uint8 v = src[0] >> (((-wt) & 1)*4);
		const uint8 *pe;
		
		switch(wt & 1) {
			do {
				v = src[0];

		case 0:	pe = &pal[3*(v&15)]; dst[1*3+0] = pe[0]; dst[1*3+1] = pe[1]; dst[1*3+2] = pe[2]; v >>= 4;
		case 1:	pe = &pal[3*(v&15)]; dst[0*3+0] = pe[0]; dst[0*3+1] = pe[1]; dst[0*3+2] = pe[2]; v >>= 4;

				dst -= 6;
				--src;
			} while((wt -= 2) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #2
0
DECLARE_PALETTED(Pal4, Any32) {
	const uint8 *src = (const uint8 *)src0;
	uint32 *dst = (uint32 *)dst0;
	const uint32 *pal = (const uint32 *)pal0;

	src += (w-1) >> 1;
	dst += ((w-1) & ~1);

	srcpitch += (w+1) >> 1;
	dstpitch += ((w+1) & ~1) * 4;

	do {
		int wt = w;

		uint8 v = src[0] >> (((-wt) & 1)*4);
		
		switch(wt & 1) {
			do {
				v = src[0];

		case 0:	dst[1] = pal[v&15];	v >>= 4;
		case 1:	dst[0] = pal[v&15];	v >>= 4;

				dst -= 2;
				--src;
			} while((wt -= 2) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #3
0
DECLARE_PALETTED(Pal2, Any32) {
	const uint8 *src = (const uint8 *)src0;
	uint32 *dst = (uint32 *)dst0;
	const uint32 *pal = (const uint32 *)pal0;

	src += (w-1) >> 2;
	dst += (w-1) & ~3;

	srcpitch += (w+3) >> 2;
	dstpitch += ((w+3) & ~3) * 4;

	do {
		int wt = w;

		uint8 v = src[0] >> (((-wt) & 3)*2);
		
		switch(wt & 3) {
			do {
				v = src[0];

		case 0:	dst[3] = pal[v&3];	v >>= 2;
		case 3:	dst[2] = pal[v&3];	v >>= 2;
		case 2:	dst[1] = pal[v&3];	v >>= 2;
		case 1:	dst[0] = pal[v&3];	v >>= 2;

				dst -= 4;
				--src;
			} while((wt -= 4) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #4
0
DECLARE_PALETTED(Pal2, Any24) {
	const uint8 *src = (const uint8 *)src0;
	uint8 *dst = (uint8 *)dst0;
	const uint8 *pal = (const uint8 *)pal0;

	src += (w-1) >> 2;
	dst += ((w-1) & ~3) * 3;

	srcpitch += (w+3) >> 2;
	dstpitch += ((w+3) & ~3) * 3;

	do {
		int wt = w;

		uint8 v = src[0] >> (((-wt) & 3)*2);
		const uint8 *pe;
		
		switch(wt & 3) {
			do {
				v = src[0];

		case 0:	pe = &pal[3*(v&3)]; dst[3*3+0] = pe[0]; dst[3*3+1] = pe[1]; dst[3*3+2] = pe[2]; v >>= 2;
		case 3:	pe = &pal[3*(v&3)]; dst[2*3+0] = pe[0]; dst[2*3+1] = pe[1]; dst[2*3+2] = pe[2]; v >>= 2;
		case 2:	pe = &pal[3*(v&3)]; dst[1*3+0] = pe[0]; dst[1*3+1] = pe[1]; dst[1*3+2] = pe[2]; v >>= 2;
		case 1:	pe = &pal[3*(v&3)]; dst[0*3+0] = pe[0]; dst[0*3+1] = pe[1]; dst[0*3+2] = pe[2]; v >>= 2;

				dst -= 12;
				--src;
			} while((wt -= 4) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #5
0
DECLARE_PALETTED(Pal8, Any32) {
	const uint8 *src = (const uint8 *)src0;
	uint32 *dst = (uint32 *)dst0;
	const uint32 *pal = (const uint32 *)pal0;

	srcpitch -= w;
	dstpitch -= w*4;

	do {
		int wt = w;

		do {
			*dst++ = pal[*src++];
		} while(--wt);

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #6
0
DECLARE_PALETTED(Pal1, Any24) {
	const uint8 *src = (const uint8 *)src0;
	uint8 *dst = (uint8 *)dst0;
	const uint8 *pal = (const uint8 *)pal0;

	src += (w-1) >> 3;
	dst += ((w-1) & ~7) * 3;

	srcpitch += (w+7) >> 3;
	dstpitch += ((w+7) & ~7) * 3;

	do {
		int wt = w;

		uint8 v = src[0] >> ((-wt) & 7);
		const uint8 *pe;
		
		switch(wt & 7) {
			do {
				v = src[0];

		case 0:	pe = &pal[3*(v&1)]; dst[7*3+0] = pe[0]; dst[7*3+1] = pe[1]; dst[7*3+2] = pe[2]; v >>= 1;
		case 7:	pe = &pal[3*(v&1)]; dst[6*3+0] = pe[0]; dst[6*3+1] = pe[1]; dst[6*3+2] = pe[2]; v >>= 1;
		case 6:	pe = &pal[3*(v&1)]; dst[5*3+0] = pe[0]; dst[5*3+1] = pe[1]; dst[5*3+2] = pe[2]; v >>= 1;
		case 5:	pe = &pal[3*(v&1)]; dst[4*3+0] = pe[0]; dst[4*3+1] = pe[1]; dst[4*3+2] = pe[2]; v >>= 1;
		case 4:	pe = &pal[3*(v&1)]; dst[3*3+0] = pe[0]; dst[3*3+1] = pe[1]; dst[3*3+2] = pe[2]; v >>= 1;
		case 3:	pe = &pal[3*(v&1)]; dst[2*3+0] = pe[0]; dst[2*3+1] = pe[1]; dst[2*3+2] = pe[2]; v >>= 1;
		case 2:	pe = &pal[3*(v&1)]; dst[1*3+0] = pe[0]; dst[1*3+1] = pe[1]; dst[1*3+2] = pe[2]; v >>= 1;
		case 1:	pe = &pal[3*(v&1)]; dst[0*3+0] = pe[0]; dst[0*3+1] = pe[1]; dst[0*3+2] = pe[2]; v >>= 1;

				dst -= 24;
				--src;
			} while((wt -= 8) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #7
0
DECLARE_PALETTED(Pal1, Any16) {
	const uint8 *src = (const uint8 *)src0;
	uint16 *dst = (uint16 *)dst0;
	const uint16 *pal = (const uint16 *)pal0;

	src += (w-1) >> 3;
	dst += (w-1) & ~7;

	srcpitch += (w+7) >> 3;
	dstpitch += ((w+7) & ~7) * 2;

	do {
		int wt = w;

		uint8 v = src[0] >> ((-wt) & 7);
		
		switch(wt & 7) {
			do {
				v = src[0];

		case 0:	dst[7] = pal[v&1];	v >>= 1;
		case 7:	dst[6] = pal[v&1];	v >>= 1;
		case 6:	dst[5] = pal[v&1];	v >>= 1;
		case 5:	dst[4] = pal[v&1];	v >>= 1;
		case 4:	dst[3] = pal[v&1];	v >>= 1;
		case 3:	dst[2] = pal[v&1];	v >>= 1;
		case 2:	dst[1] = pal[v&1];	v >>= 1;
		case 1:	dst[0] = pal[v&1];	v >>= 1;

				dst -= 8;
				--src;
			} while((wt -= 8) > 0);
		}

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #8
0
DECLARE_PALETTED(Pal8, Any24) {
	const uint8 *src = (const uint8 *)src0;
	uint8 *dst = (uint8 *)dst0;
	const uint8 *pal = (const uint8 *)pal0;

	srcpitch -= w;
	dstpitch -= w*3;

	do {
		int wt = w;
		do {
			const uint8 *pe = &pal[3**src++];

			dst[0] = pe[0];
			dst[1] = pe[1];
			dst[2] = pe[2];
			dst += 3;
		} while(--wt);

		vdptrstep(src, srcpitch);
		vdptrstep(dst, dstpitch);
	} while(--h);
}
Beispiel #9
0
void VDCreateTestPal8Video(VDGUIHandle h) {
	CPUEnableExtensions(CPUCheckForExtensions());

	try {
		tVDInputDrivers inputDrivers;
		std::vector<int> xlat;

		VDGetInputDrivers(inputDrivers, IVDInputDriver::kF_Video);

		const VDStringW filter(VDMakeInputDriverFileFilter(inputDrivers, xlat));

		const VDFileDialogOption opt[]={
			{ VDFileDialogOption::kSelectedFilter },
			0
		};

		int optval[1]={0};

		const VDStringW srcfile(VDGetLoadFileName('pl8s', h, L"Choose source file", filter.c_str(), NULL, opt, optval));

		if (srcfile.empty())
			return;

		IVDInputDriver *pDrv;
		int filtidx = xlat[optval[0] - 1];
		if (filtidx < 0)
			pDrv = VDAutoselectInputDriverForFile(srcfile.c_str(), IVDInputDriver::kF_Video);
		else {
			tVDInputDrivers::iterator itDrv(inputDrivers.begin());
			std::advance(itDrv, filtidx);

			pDrv = *itDrv;
		}

		vdrefptr<InputFile> pIF(pDrv->CreateInputFile(0));

		pIF->Init(srcfile.c_str());

		const VDStringW dstfile(VDGetSaveFileName('pl8d', h, L"Choose destination 8-bit file", L"Audio-video interleaved (*.avi)\0*.avi\0All files\0*.*", L"avi", NULL, NULL));
		if (dstfile.empty())
			return;

		vdrefptr<IVDVideoSource> pVS;
		pIF->GetVideoSource(0, ~pVS);
		IVDStreamSource *pVSS = pVS->asStream();
		const VDPosition frames = pVSS->getLength();

		if (!pVS->setTargetFormat(nsVDPixmap::kPixFormat_XRGB8888))
			throw MyError("Cannot set decompression format to 32-bit.");

		vdautoptr<IVDMediaOutputAVIFile> pOut(VDCreateMediaOutputAVIFile());

		IVDMediaOutputStream *pVSOut = pOut->createVideoStream();

		const VDPixmap& pxsrc = pVS->getTargetFormat();
		const uint32 rowbytes = (pxsrc.w+3) & ~3;

		AVIStreamHeader_fixed hdr;

		hdr.fccType		= 'sdiv';
		hdr.fccHandler	= 0;
		hdr.dwFlags		= 0;
		hdr.wPriority	= 0;
		hdr.wLanguage	= 0;
		hdr.dwScale		= pVSS->getStreamInfo().dwScale;
		hdr.dwRate		= pVSS->getStreamInfo().dwRate;
		hdr.dwStart		= 0;
		hdr.dwLength	= 0;
		hdr.dwInitialFrames = 0;
		hdr.dwSuggestedBufferSize = 0;
		hdr.dwQuality = -1;
		hdr.dwSampleSize = 0;
		hdr.rcFrame.left	= 0;
		hdr.rcFrame.top		= 0;
		hdr.rcFrame.right	= (short)pxsrc.w;
		hdr.rcFrame.bottom	= (short)pxsrc.h;

		pVSOut->setStreamInfo(hdr);

		vdstructex<BITMAPINFOHEADER> bih;

		bih.resize(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*252);

		bih->biSize = sizeof(BITMAPINFOHEADER);
		bih->biWidth = pxsrc.w;
		bih->biHeight = pxsrc.h;
		bih->biPlanes = 1;
		bih->biBitCount = 8;
		bih->biCompression = BI_RGB;
		bih->biSizeImage = rowbytes*pxsrc.h;
		bih->biXPelsPerMeter = 0;
		bih->biYPelsPerMeter = 0;
		bih->biClrUsed = 252;
		bih->biClrImportant = 252;

		RGBQUAD *pal = (RGBQUAD *)((char *)bih.data() + sizeof(BITMAPINFOHEADER));
		for(int i=0; i<252; ++i) {
			pal[i].rgbRed		= (BYTE)((i/42)*51);
			pal[i].rgbGreen		= (BYTE)((((i/6)%7)*85)>>1);
			pal[i].rgbBlue		= (BYTE)((i%6)*51);
			pal[i].rgbReserved	= 0;
		}

		pVSOut->setFormat(bih.data(), bih.size());

		pOut->init(dstfile.c_str());

		ProgressDialog dlg((HWND)h, "Processing video stream", "Palettizing frames", (long)frames, true);

		vdblock<uint8> outbuf(rowbytes * pxsrc.h);

		const vdpixsize w = pxsrc.w;
		const vdpixsize h = pxsrc.h;

		try {
			for(uint32 frame=0; frame<frames; ++frame) {
				pVS->getFrame(frame);

				const uint8 *src = (const uint8 *)pxsrc.data;
				ptrdiff_t srcpitch = pxsrc.pitch;
				uint8 *dst = &outbuf[rowbytes * (pxsrc.h - 1)];

				for(int y=0; y<h; ++y) {
					const uint8 *dr = ditherred[y & 15];
					const uint8 *dg = dithergrn[y & 15];
					const uint8 *db = ditherblu[y & 15];

					for(int x=0; x<w; ++x) {
						const uint8 b = (uint8)((((src[0] * 1286)>>8) + dr[x&15]) >> 8);
						const uint8 g = (uint8)((((src[1] * 1543)>>8) + dg[x&15]) >> 8);
						const uint8 r = (uint8)((((src[2] * 1286)>>8) + db[x&15]) >> 8);
						src += 4;

						dst[x] = (uint8)(r*42 + g*6 + b);
					}

					vdptrstep(dst, -(ptrdiff_t)rowbytes);
					vdptrstep(src, srcpitch - w*4);
				}

				pVSOut->write(AVIOutputStream::kFlagKeyFrame, outbuf.data(), outbuf.size(), 1);

				dlg.advance(frame);
				dlg.check();
			}
		} catch(const MyUserAbortError&) {
		}

		pVSOut->flush();
		pOut->finalize();

	} catch(const MyError& e) {
		e.post((HWND)h, g_szError);
	}
}
Beispiel #10
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);
}