Пример #1
0
FontPlatformData::FontPlatformData(HFONT font, float size, bool bold, bool oblique, bool useGDI)
    : m_font(RefCountedGDIHandle<HFONT>::create(font))
    , m_size(size)
#if PLATFORM(CG)
    , m_cgFont(0)
#elif PLATFORM(CAIRO)
    , m_fontFace(0)
    , m_scaledFont(0)
#endif
    , m_syntheticBold(bold)
    , m_syntheticOblique(oblique)
    , m_useGDI(useGDI)
{
    HDC hdc = GetDC(0);
    SaveDC(hdc);
    
    SelectObject(hdc, font);
    UINT bufferSize = GetOutlineTextMetricsW(hdc, 0, NULL);

    ASSERT_WITH_MESSAGE(bufferSize, "Bitmap fonts not supported with CoreGraphics.");

    if (bufferSize) {
        OUTLINETEXTMETRICW* metrics = (OUTLINETEXTMETRICW*)malloc(bufferSize);

        GetOutlineTextMetricsW(hdc, bufferSize, metrics);
        WCHAR* faceName = (WCHAR*)((uintptr_t)metrics + (uintptr_t)metrics->otmpFaceName);

        platformDataInit(font, size, hdc, faceName);

        free(metrics);
    }

    RestoreDC(hdc, -1);
    ReleaseDC(0, hdc);
}
Пример #2
0
static OUTLINETEXTMETRICW *getOutlineTextMetric(HDC hdc)
{
    int size;
    size = GetOutlineTextMetricsW(hdc, 0, 0);
    OUTLINETEXTMETRICW *otm = (OUTLINETEXTMETRICW *)malloc(size);
    GetOutlineTextMetricsW(hdc, size, otm);
    return otm;
}
Пример #3
0
static void gdi_get_font_metrics(LOGFONTW *lf, struct font_metrics *fm)
{
    HDC hdc;
    HFONT hfont;
    NEWTEXTMETRICW ntm;
    OUTLINETEXTMETRICW otm;
    int ret;

    hdc = CreateCompatibleDC(0);

    /* it's the only way to get extended NEWTEXTMETRIC fields */
    ret = EnumFontFamiliesExW(hdc, lf, font_enum_proc, (LPARAM)&ntm, 0);
    ok(!ret, "EnumFontFamiliesExW failed to find %s\n", wine_dbgstr_w(lf->lfFaceName));

    hfont = CreateFontIndirectW(lf);
    SelectObject(hdc, hfont);

    otm.otmSize = sizeof(otm);
    ret = GetOutlineTextMetricsW(hdc, otm.otmSize, &otm);
    ok(ret, "GetOutlineTextMetrics failed\n");

    DeleteDC(hdc);
    DeleteObject(hfont);

    fm->lfHeight = -otm.otmTextMetrics.tmAscent;
    fm->line_spacing = ntm.ntmCellHeight;
    fm->font_size = (REAL)otm.otmTextMetrics.tmAscent;
    fm->font_height = (REAL)fm->line_spacing * fm->font_size / (REAL)ntm.ntmSizeEM;
    fm->em_height = ntm.ntmSizeEM;
    fm->ascent = ntm.ntmSizeEM;
    fm->descent = ntm.ntmCellHeight - ntm.ntmSizeEM;
}
Пример #4
0
FontPlatformData::FontPlatformData(GDIObject<HFONT> font, float size, bool bold, bool oblique, bool useGDI)
    : m_font(SharedGDIObject<HFONT>::create(WTFMove(font)))
    , m_size(size)
    , m_orientation(Horizontal)
    , m_widthVariant(RegularWidth)
    , m_isColorBitmapFont(false)
    , m_syntheticBold(bold)
    , m_syntheticOblique(oblique)
    , m_useGDI(useGDI)
{
    HWndDC hdc(0);
    SaveDC(hdc);
    
    ::SelectObject(hdc, m_font->get());
    UINT bufferSize = GetOutlineTextMetrics(hdc, 0, NULL);

    ASSERT_WITH_MESSAGE(bufferSize, "Bitmap fonts not supported with CoreGraphics.");

    if (bufferSize) {
        OUTLINETEXTMETRICW* metrics = (OUTLINETEXTMETRICW*)malloc(bufferSize);

        GetOutlineTextMetricsW(hdc, bufferSize, metrics);
        WCHAR* faceName = (WCHAR*)((uintptr_t)metrics + (uintptr_t)metrics->otmpFaceName);

        platformDataInit(m_font->get(), size, hdc, faceName);

        free(metrics);
    }

    RestoreDC(hdc, -1);
}
Пример #5
0
LONG WINAPI TTGetEmbeddingType(HDC hDC, ULONG *status)
{
    OUTLINETEXTMETRICW otm;

    TRACE("(%p %p)\n", hDC, status);

    if (!hDC)
        return E_HDCINVALID;

    otm.otmSize = sizeof(otm);
    if (!GetOutlineTextMetricsW(hDC, otm.otmSize, &otm))
        return E_NOTATRUETYPEFONT;

    if (!status)
        return E_PERMISSIONSINVALID;

    if (otm.otmfsType == LICENSE_INSTALLABLE)
        *status = EMBED_INSTALLABLE;
    else if (otm.otmfsType & LICENSE_NOEMBEDDING)
        *status = EMBED_NOEMBEDDING;
    else if (otm.otmfsType & LICENSE_PREVIEWPRINT)
        *status = EMBED_PREVIEWPRINT;
    else if (otm.otmfsType & LICENSE_EDITABLE)
        *status = EMBED_EDITABLE;

    return E_NONE;
}
Пример #6
0
static void test_GetMetrics(void)
{
    IDWriteGdiInterop *interop;
    DWRITE_FONT_METRICS metrics;
    OUTLINETEXTMETRICW otm;
    IDWriteFont *font;
    LOGFONTW logfont;
    HRESULT hr;
    HDC hdc;
    HFONT hfont;
    int ret;

    hr = IDWriteFactory_GetGdiInterop(factory, &interop);
    EXPECT_HR(hr, S_OK);

    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = FW_NORMAL;
    logfont.lfItalic = 1;
    lstrcpyW(logfont.lfFaceName, tahomaW);

    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hfont = CreateFontIndirectW(&logfont);
    hdc = CreateCompatibleDC(0);
    SelectObject(hdc, hfont);

    otm.otmSize = sizeof(otm);
    ret = GetOutlineTextMetricsW(hdc, otm.otmSize, &otm);
    ok(ret, "got %d\n", ret);
    DeleteDC(hdc);
    DeleteObject(hfont);

    if (0) /* crashes on native */
        IDWriteFont_GetMetrics(font, NULL);

    memset(&metrics, 0, sizeof(metrics));
    IDWriteFont_GetMetrics(font, &metrics);

    ok(metrics.designUnitsPerEm != 0, "designUnitsPerEm %u\n", metrics.designUnitsPerEm);
    ok(metrics.ascent != 0, "ascent %u\n", metrics.ascent);
    ok(metrics.descent != 0, "descent %u\n", metrics.descent);
    todo_wine
    ok(metrics.lineGap == 0, "lineGap %d\n", metrics.lineGap);
    ok(metrics.capHeight, "capHeight %u\n", metrics.capHeight);
    ok(metrics.xHeight != 0, "xHeight %u\n", metrics.xHeight);
    ok(metrics.underlinePosition < 0, "underlinePosition %d\n", metrics.underlinePosition);
    ok(metrics.underlineThickness != 0, "underlineThickness %u\n", metrics.underlineThickness);
    ok(metrics.strikethroughPosition > 0, "strikethroughPosition %d\n", metrics.strikethroughPosition);
    ok(metrics.strikethroughThickness != 0, "strikethroughThickness %u\n", metrics.strikethroughThickness);

    IDWriteFont_Release(font);
    IDWriteGdiInterop_Release(interop);
}
Пример #7
0
/*******************************************************************************
 * GdipCreateFont [GDIPLUS.@]
 *
 * Create a new font based off of a FontFamily
 *
 * PARAMS
 *  *fontFamily     [I] Family to base the font off of
 *  emSize          [I] Size of the font
 *  style           [I] Bitwise OR of FontStyle enumeration
 *  unit            [I] Unit emSize is measured in
 *  **font          [I] the resulting Font object
 *
 * RETURNS
 *  SUCCESS: Ok
 *  FAILURE: InvalidParameter if fontfamily or font is NULL.
 *  FAILURE: FontFamilyNotFound if an invalid FontFamily is given
 *
 * NOTES
 *  UnitDisplay is unsupported.
 *  emSize is stored separately from lfHeight, to hold the fraction.
 */
GpStatus WINGDIPAPI GdipCreateFont(GDIPCONST GpFontFamily *fontFamily,
                        REAL emSize, INT style, Unit unit, GpFont **font)
{
    HFONT hfont;
    OUTLINETEXTMETRICW otm;
    LOGFONTW lfw;
    HDC hdc;
    GpStatus stat;
    int ret;

    if (!fontFamily || !font || emSize < 0.0)
        return InvalidParameter;

    TRACE("%p (%s), %f, %d, %d, %p\n", fontFamily,
            debugstr_w(fontFamily->FamilyName), emSize, style, unit, font);

    memset(&lfw, 0, sizeof(lfw));

    stat = GdipGetFamilyName(fontFamily, lfw.lfFaceName, LANG_NEUTRAL);
    if (stat != Ok) return stat;

    lfw.lfHeight = -units_to_pixels(emSize, unit, fontFamily->dpi);
    lfw.lfWeight = style & FontStyleBold ? FW_BOLD : FW_REGULAR;
    lfw.lfItalic = style & FontStyleItalic;
    lfw.lfUnderline = style & FontStyleUnderline;
    lfw.lfStrikeOut = style & FontStyleStrikeout;

    hfont = CreateFontIndirectW(&lfw);
    hdc = CreateCompatibleDC(0);
    SelectObject(hdc, hfont);
    otm.otmSize = sizeof(otm);
    ret = GetOutlineTextMetricsW(hdc, otm.otmSize, &otm);
    DeleteDC(hdc);
    DeleteObject(hfont);

    if (!ret) return NotTrueTypeFont;

    *font = GdipAlloc(sizeof(GpFont));
    if (!*font) return OutOfMemory;

    (*font)->unit = unit;
    (*font)->emSize = emSize;
    (*font)->otm = otm;

    stat = clone_font_family(fontFamily, &(*font)->family);
    if (stat != Ok)
    {
        GdipFree(*font);
        return stat;
    }

    TRACE("<-- %p\n", *font);

    return Ok;
}
Пример #8
0
static BOOL get_font_metrics(HDC hdc, struct font_metrics *fm)
{
    OUTLINETEXTMETRICW otm;
    TT_OS2_V2 tt_os2;
    TT_HHEA tt_hori;
    LONG size;
    UINT16 line_gap;

    otm.otmSize = sizeof(otm);
    if (!GetOutlineTextMetricsW(hdc, otm.otmSize, &otm)) return FALSE;

    fm->em_height = otm.otmEMSquare;
    fm->dpi = GetDeviceCaps(hdc, LOGPIXELSY);

    memset(&tt_hori, 0, sizeof(tt_hori));
    if (GetFontData(hdc, MS_HHEA_TAG, 0, &tt_hori, sizeof(tt_hori)) != GDI_ERROR)
    {
        fm->ascent = GET_BE_WORD(tt_hori.Ascender);
        fm->descent = -GET_BE_WORD(tt_hori.Descender);
        TRACE("hhea: ascent %d, descent %d\n", fm->ascent, fm->descent);
        line_gap = GET_BE_WORD(tt_hori.LineGap);
        fm->line_spacing = fm->ascent + fm->descent + line_gap;
        TRACE("line_gap %u, line_spacing %u\n", line_gap, fm->line_spacing);
        if (fm->ascent + fm->descent != 0) return TRUE;
    }

    size = GetFontData(hdc, MS_OS2_TAG, 0, NULL, 0);
    if (size == GDI_ERROR) return FALSE;

    if (size > sizeof(tt_os2)) size = sizeof(tt_os2);

    memset(&tt_os2, 0, sizeof(tt_os2));
    if (GetFontData(hdc, MS_OS2_TAG, 0, &tt_os2, size) != size) return FALSE;

    fm->ascent = GET_BE_WORD(tt_os2.usWinAscent);
    fm->descent = GET_BE_WORD(tt_os2.usWinDescent);
    TRACE("usWinAscent %u, usWinDescent %u\n", fm->ascent, fm->descent);
    if (fm->ascent + fm->descent == 0)
    {
        fm->ascent = GET_BE_WORD(tt_os2.sTypoAscender);
        fm->descent = GET_BE_WORD(tt_os2.sTypoDescender);
        TRACE("sTypoAscender %u, sTypoDescender %u\n", fm->ascent, fm->descent);
    }
    line_gap = GET_BE_WORD(tt_os2.sTypoLineGap);
    fm->line_spacing = fm->ascent + fm->descent + line_gap;
    TRACE("line_gap %u, line_spacing %u\n", line_gap, fm->line_spacing);
    return TRUE;
}
Пример #9
0
/*******************************************************************************
 * GdipCreateFontFromLogfontW [GDIPLUS.@]
 */
GpStatus WINGDIPAPI GdipCreateFontFromLogfontW(HDC hdc,
    GDIPCONST LOGFONTW *logfont, GpFont **font)
{
    HFONT hfont, oldfont;
    OUTLINETEXTMETRICW otm;
    GpStatus stat;
    int ret;

    TRACE("(%p, %p, %p)\n", hdc, logfont, font);

    if (!hdc || !logfont || !font)
        return InvalidParameter;

    hfont = CreateFontIndirectW(logfont);
    oldfont = SelectObject(hdc, hfont);
    otm.otmSize = sizeof(otm);
    ret = GetOutlineTextMetricsW(hdc, otm.otmSize, &otm);
    SelectObject(hdc, oldfont);
    DeleteObject(hfont);

    if (!ret) return NotTrueTypeFont;

    *font = GdipAlloc(sizeof(GpFont));
    if (!*font) return OutOfMemory;

    (*font)->unit = UnitWorld;
    (*font)->emSize = otm.otmTextMetrics.tmAscent;
    (*font)->otm = otm;

    stat = GdipCreateFontFamilyFromName(logfont->lfFaceName, NULL, &(*font)->family);
    if (stat != Ok)
    {
        GdipFree(*font);
        return NotTrueTypeFont;
    }

    TRACE("<-- %p\n", *font);

    return Ok;
}
Пример #10
0
static void test_CreateFontFromLOGFONT(void)
{
    static const WCHAR tahomaspW[] = {'T','a','h','o','m','a',' ',0};
    IDWriteGdiInterop *interop;
    DWRITE_FONT_WEIGHT weight;
    DWRITE_FONT_STYLE style;
    IDWriteFont *font;
    LOGFONTW logfont;
    LONG weights[][2] = {
        {FW_NORMAL, DWRITE_FONT_WEIGHT_NORMAL},
        {FW_BOLD, DWRITE_FONT_WEIGHT_BOLD},
        {  0, DWRITE_FONT_WEIGHT_NORMAL},
        { 50, DWRITE_FONT_WEIGHT_NORMAL},
        {150, DWRITE_FONT_WEIGHT_NORMAL},
        {250, DWRITE_FONT_WEIGHT_NORMAL},
        {350, DWRITE_FONT_WEIGHT_NORMAL},
        {450, DWRITE_FONT_WEIGHT_NORMAL},
        {650, DWRITE_FONT_WEIGHT_BOLD},
        {750, DWRITE_FONT_WEIGHT_BOLD},
        {850, DWRITE_FONT_WEIGHT_BOLD},
        {950, DWRITE_FONT_WEIGHT_BOLD},
        {960, DWRITE_FONT_WEIGHT_BOLD},
    };
    OUTLINETEXTMETRICW otm;
    HRESULT hr;
    BOOL ret;
    HDC hdc;
    HFONT hfont;
    int i;
    UINT r;

    hr = IDWriteFactory_GetGdiInterop(factory, &interop);
    EXPECT_HR(hr, S_OK);

    if (0)
        /* null out parameter crashes this call */
        hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, NULL, NULL);

    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, NULL, &font);
    EXPECT_HR(hr, E_INVALIDARG);

    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = FW_NORMAL;
    logfont.lfItalic = 1;
    lstrcpyW(logfont.lfFaceName, tahomaW);

    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    EXPECT_HR(hr, S_OK);

    hfont = CreateFontIndirectW(&logfont);
    hdc = CreateCompatibleDC(0);
    SelectObject(hdc, hfont);

    otm.otmSize = sizeof(otm);
    r = GetOutlineTextMetricsW(hdc, otm.otmSize, &otm);
    ok(r, "got %d\n", r);
    DeleteDC(hdc);
    DeleteObject(hfont);

    /* now check properties */
    weight = IDWriteFont_GetWeight(font);
    ok(weight == DWRITE_FONT_WEIGHT_NORMAL, "got %d\n", weight);

    style = IDWriteFont_GetStyle(font);
    todo_wine {
        ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);
        ok(otm.otmfsSelection == 1, "got 0x%08x\n", otm.otmfsSelection);
    }
    ret = IDWriteFont_IsSymbolFont(font);
    ok(!ret, "got %d\n", ret);

    IDWriteFont_Release(font);

    /* weight values */
    for (i = 0; i < sizeof(weights)/(2*sizeof(LONG)); i++)
    {
        memset(&logfont, 0, sizeof(logfont));
        logfont.lfHeight = 12;
        logfont.lfWidth  = 12;
        logfont.lfWeight = weights[i][0];
        lstrcpyW(logfont.lfFaceName, tahomaW);

        hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
        EXPECT_HR(hr, S_OK);

        weight = IDWriteFont_GetWeight(font);
        ok(weight == weights[i][1],
           "%d: got %d, expected %d\n", i, weight, weights[i][1]);
        IDWriteFont_Release(font);
    }

    /* weight not from enum */
    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = 550;
    lstrcpyW(logfont.lfFaceName, tahomaW);

    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    EXPECT_HR(hr, S_OK);

    weight = IDWriteFont_GetWeight(font);
    ok(weight == DWRITE_FONT_WEIGHT_NORMAL || broken(weight == DWRITE_FONT_WEIGHT_BOLD) /* win7 w/o SP */,
       "got %d\n", weight);
    IDWriteFont_Release(font);

    /* empty or nonexistent face name */
    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = FW_NORMAL;
    lstrcpyW(logfont.lfFaceName, blahW);

    font = (void*)0xdeadbeef;
    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    todo_wine {
        EXPECT_HR(hr, DWRITE_E_NOFONT);
        ok(font == NULL, "got %p\n", font);
        if(font) IDWriteFont_Release(font);
    }

    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = FW_NORMAL;
    lstrcpyW(logfont.lfFaceName, tahomaspW);

    font = (void*)0xdeadbeef;
    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    todo_wine {
        EXPECT_HR(hr, DWRITE_E_NOFONT);
        ok(font == NULL, "got %p\n", font);
        if(font) IDWriteFont_Release(font);
    }

    memset(&logfont, 0, sizeof(logfont));
    logfont.lfHeight = 12;
    logfont.lfWidth  = 12;
    logfont.lfWeight = FW_NORMAL;

    font = (void*)0xdeadbeef;
    hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
    todo_wine {
        EXPECT_HR(hr, DWRITE_E_NOFONT);
        ok(font == NULL, "got %p\n", font);
        if(font) IDWriteFont_Release(font);
    }

    IDWriteGdiInterop_Release(interop);
}
Пример #11
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);
}