diff options
Diffstat (limited to 'libass/ass_directwrite.c')
-rw-r--r-- | libass/ass_directwrite.c | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/libass/ass_directwrite.c b/libass/ass_directwrite.c new file mode 100644 index 0000000..483af50 --- /dev/null +++ b/libass/ass_directwrite.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2015 Stephan Vedder <stephan.vedder@gmail.com> + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#define COBJMACROS + +#include "config.h" + +#include <initguid.h> +#include <ole2.h> +#include <shobjidl.h> + +#include "dwrite.h" + +DEFINE_GUID(IID_IDWriteFactory, 0xb859ee5a,0xd838,0x4b5b,0xa2,0xe8,0x1a,0xdc,0x7d,0x93,0xdb,0x48); +DEFINE_GUID(IID_IDWritePixelSnapping, 0xeaf3a2da,0xecf4,0x4d24,0xb6,0x44,0xb3,0x4f,0x68,0x42,0x02,0x4b); +DEFINE_GUID(IID_IDWriteTextRenderer, 0xef8a8135,0x5cc6,0x45fe,0x88,0x25,0xc5,0xa0,0x72,0x4e,0xb8,0x19); + +#include "ass_directwrite.h" +#include "ass_utils.h" + +#define NAME_MAX_LENGTH 256 +#define FALLBACK_DEFAULT_FONT L"Arial" + +/* + * The private data stored for every font, detected by this backend. + */ +typedef struct { + IDWriteFont *font; + IDWriteFontFileStream *stream; +} FontPrivate; + +typedef struct { + HMODULE directwrite_lib; + IDWriteFactory *factory; +} ProviderPrivate; + +/** + * Custom text renderer class for logging the fonts used. It does not + * actually render anything or do anything apart from that. + */ + +typedef struct FallbackLogTextRenderer { + IDWriteTextRenderer iface; + IDWriteTextRendererVtbl vtbl; + IDWriteFactory *dw_factory; + LONG ref_count; +} FallbackLogTextRenderer; + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_IsPixelSnappingDisabled( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + _Out_ BOOL* isDisabled + ) +{ + *isDisabled = true; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_GetCurrentTransform( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + _Out_ DWRITE_MATRIX* transform + ) +{ + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_GetPixelsPerDip( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + _Out_ FLOAT* pixelsPerDip + ) +{ + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_DrawGlyphRun( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, + _In_ DWRITE_GLYPH_RUN const* glyphRun, + _In_ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect + ) +{ + FallbackLogTextRenderer *this = (FallbackLogTextRenderer *)This; + HRESULT hr; + IDWriteFontCollection *font_coll = NULL; + IDWriteFont **font = (IDWriteFont **)clientDrawingContext; + + hr = IDWriteFactory_GetSystemFontCollection(this->dw_factory, &font_coll, FALSE); + if (FAILED(hr)) + return E_FAIL; + + hr = IDWriteFontCollection_GetFontFromFontFace(font_coll, glyphRun->fontFace, + font); + if (FAILED(hr)) + return E_FAIL; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_DrawUnderline( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + _In_ DWRITE_UNDERLINE const* underline, + IUnknown* clientDrawingEffect + ) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_DrawStrikethrough( + IDWriteTextRenderer *This, + _In_opt_ void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + _In_ DWRITE_STRIKETHROUGH const* strikethrough, + IUnknown* clientDrawingEffect + ) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_DrawInlineObject( + IDWriteTextRenderer *This, + void *clientDrawingContext, + FLOAT originX, + FLOAT originY, + IDWriteInlineObject *inlineObject, + WINBOOL isSideways, + WINBOOL isRightToLeft, + IUnknown *clientDrawingEffect + ) +{ + return S_OK; +} + +// IUnknown methods + +static ULONG STDMETHODCALLTYPE FallbackLogTextRenderer_AddRef( + IDWriteTextRenderer *This + ) +{ + FallbackLogTextRenderer *this = (FallbackLogTextRenderer *)This; + return InterlockedIncrement(&this->ref_count); +} + +static ULONG STDMETHODCALLTYPE FallbackLogTextRenderer_Release( + IDWriteTextRenderer *This + ) +{ + FallbackLogTextRenderer *this = (FallbackLogTextRenderer *)This; + unsigned long new_count = InterlockedDecrement(&this->ref_count); + if (new_count == 0) { + free(this); + return 0; + } + + return new_count; +} + +static HRESULT STDMETHODCALLTYPE FallbackLogTextRenderer_QueryInterface( + IDWriteTextRenderer *This, + REFIID riid, + void **ppvObject + ) +{ + if (IsEqualGUID(riid, &IID_IDWriteTextRenderer) + || IsEqualGUID(riid, &IID_IDWritePixelSnapping) + || IsEqualGUID(riid, &IID_IUnknown)) { + *ppvObject = This; + } else { + *ppvObject = NULL; + return E_FAIL; + } + + This->lpVtbl->AddRef(This); + return S_OK; +} + +static void init_FallbackLogTextRenderer(FallbackLogTextRenderer *r, + IDWriteFactory *factory) +{ + *r = (FallbackLogTextRenderer){ + .iface = { + .lpVtbl = &r->vtbl, + }, + .vtbl = { + FallbackLogTextRenderer_QueryInterface, + FallbackLogTextRenderer_AddRef, + FallbackLogTextRenderer_Release, + FallbackLogTextRenderer_IsPixelSnappingDisabled, + FallbackLogTextRenderer_GetCurrentTransform, + FallbackLogTextRenderer_GetPixelsPerDip, + FallbackLogTextRenderer_DrawGlyphRun, + FallbackLogTextRenderer_DrawUnderline, + FallbackLogTextRenderer_DrawStrikethrough, + FallbackLogTextRenderer_DrawInlineObject, + }, + .dw_factory = factory, + }; +} + +/* + * This function is called whenever a font is used for the first + * time. It will create a FontStream for memory reading, which + * will be stored within the private data. + */ +static bool init_font_private(FontPrivate *priv) +{ + HRESULT hr = S_OK; + IDWriteFont *font = priv->font; + IDWriteFontFace *face = NULL; + IDWriteFontFile *file = NULL; + IDWriteFontFileStream *stream = NULL; + IDWriteFontFileLoader *loader = NULL; + UINT32 n_files = 1; + const void *refKey = NULL; + UINT32 keySize = 0; + + if (priv->stream != NULL) + return true; + + hr = IDWriteFont_CreateFontFace(font, &face); + if (FAILED(hr) || !face) + return false; + + /* DirectWrite only supports one file per face */ + hr = IDWriteFontFace_GetFiles(face, &n_files, &file); + if (FAILED(hr) || !file) { + IDWriteFontFace_Release(face); + return false; + } + + hr = IDWriteFontFile_GetReferenceKey(file, &refKey, &keySize); + if (FAILED(hr)) { + IDWriteFontFile_Release(file); + IDWriteFontFace_Release(face); + return false; + } + + hr = IDWriteFontFile_GetLoader(file, &loader); + if (FAILED(hr) || !loader) { + IDWriteFontFile_Release(file); + IDWriteFontFace_Release(face); + return false; + } + + hr = IDWriteFontFileLoader_CreateStreamFromKey(loader, refKey, keySize, &stream); + if (FAILED(hr) || !stream) { + IDWriteFontFile_Release(file); + IDWriteFontFace_Release(face); + return false; + } + + priv->stream = stream; + IDWriteFontFile_Release(file); + IDWriteFontFace_Release(face); + + return true; +} + +/* + * Read a specified part of a fontfile into memory. + * If the font wasn't used before first creates a + * FontStream and save it into the private data for later usage. + * If the parameter "buf" is NULL libass wants to know the + * size of the Fontfile + */ +static size_t get_data(void *data, unsigned char *buf, size_t offset, + size_t length) +{ + HRESULT hr = S_OK; + FontPrivate *priv = (FontPrivate *) data; + const void *fileBuf = NULL; + void *fragContext = NULL; + + if (!init_font_private(priv)) + return 0; + + if (buf == NULL) { + UINT64 fileSize; + hr = IDWriteFontFileStream_GetFileSize(priv->stream, &fileSize); + if (FAILED(hr)) + return 0; + + return fileSize; + } + + hr = IDWriteFontFileStream_ReadFileFragment(priv->stream, &fileBuf, offset, + length, &fragContext); + + if (FAILED(hr) || !fileBuf) + return 0; + + memcpy(buf, fileBuf, length); + + IDWriteFontFileStream_ReleaseFileFragment(priv->stream, fragContext); + + return length; +} + +/* + * Checks if the passed font has a specific unicode + * character. Returns 0 for failure and 1 for success + */ +static int check_glyph(void *data, uint32_t code) +{ + HRESULT hr = S_OK; + FontPrivate *priv = (FontPrivate *) data; + BOOL exists = FALSE; + + if (code == 0) + return 1; + + IDWriteFont_HasCharacter(priv->font, code, &exists); + if (FAILED(hr)) + return 0; + + return exists; +} + +/* + * This will release the directwrite backend + */ +static void destroy_provider(void *priv) +{ + ProviderPrivate *provider_priv = (ProviderPrivate *)priv; + provider_priv->factory->lpVtbl->Release(provider_priv->factory); + FreeLibrary(provider_priv->directwrite_lib); + free(provider_priv); +} + +/* + * This will destroy a specific font and it's + * Fontstream (in case it does exist) + */ + +static void destroy_font(void *data) +{ + FontPrivate *priv = (FontPrivate *) data; + + IDWriteFont_Release(priv->font); + if (priv->stream != NULL) + IDWriteFontFileStream_Release(priv->stream); + + free(priv); +} + +static int encode_utf16(wchar_t *chars, uint32_t codepoint) +{ + if (codepoint < 0x10000) { + chars[0] = codepoint; + return 1; + } else { + chars[0] = (codepoint >> 10) + 0xD7C0; + chars[1] = (codepoint & 0x3FF) + 0xDC00; + return 2; + } +} + +static char *get_fallback(void *priv, ASS_FontProviderMetaData *meta, + uint32_t codepoint) +{ + HRESULT hr; + ProviderPrivate *provider_priv = (ProviderPrivate *)priv; + IDWriteFactory *dw_factory = provider_priv->factory; + IDWriteTextFormat *text_format = NULL; + IDWriteTextLayout *text_layout = NULL; + FallbackLogTextRenderer renderer; + + init_FallbackLogTextRenderer(&renderer, dw_factory); + + hr = IDWriteFactory_CreateTextFormat(dw_factory, FALLBACK_DEFAULT_FONT, NULL, + DWRITE_FONT_WEIGHT_MEDIUM, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, 1.0f, L"", &text_format); + if (FAILED(hr)) { + return NULL; + } + + // Encode codepoint as UTF-16 + wchar_t char_string[2]; + int char_len = encode_utf16(char_string, codepoint); + + // Create a text_layout, a high-level text rendering facility, using + // the given codepoint and dummy format. + hr = IDWriteFactory_CreateTextLayout(dw_factory, char_string, char_len, text_format, + 0.0f, 0.0f, &text_layout); + if (FAILED(hr)) { + IDWriteTextFormat_Release(text_format); + return NULL; + } + + // Draw the layout with a dummy renderer, which logs the + // font used and stores it. + IDWriteFont *font = NULL; + hr = IDWriteTextLayout_Draw(text_layout, &font, &renderer.iface, 0.0f, 0.0f); + if (FAILED(hr) || font == NULL) { + IDWriteTextLayout_Release(text_layout); + IDWriteTextFormat_Release(text_format); + return NULL; + } + + // We're done with these now + IDWriteTextLayout_Release(text_layout); + IDWriteTextFormat_Release(text_format); + + // Now, just extract the first family name + BOOL exists = FALSE; + IDWriteLocalizedStrings *familyNames = NULL; + hr = IDWriteFont_GetInformationalStrings(font, + DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, + &familyNames, &exists); + if (FAILED(hr) || !exists) { + IDWriteFont_Release(font); + return NULL; + } + + wchar_t temp_name[NAME_MAX_LENGTH]; + hr = IDWriteLocalizedStrings_GetString(familyNames, 0, temp_name, NAME_MAX_LENGTH); + if (FAILED(hr)) { + IDWriteLocalizedStrings_Release(familyNames); + IDWriteFont_Release(font); + return NULL; + } + temp_name[NAME_MAX_LENGTH-1] = 0; + + // DirectWrite may not have found a valid fallback, so check that + // the selected font actually has the requested glyph. + hr = IDWriteFont_HasCharacter(font, codepoint, &exists); + if (FAILED(hr) || !exists) { + IDWriteLocalizedStrings_Release(familyNames); + IDWriteFont_Release(font); + return NULL; + } + + int size_needed = WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, NULL, 0,NULL, NULL); + char *family = (char *) malloc(size_needed); + WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, family, size_needed, NULL, NULL); + + IDWriteLocalizedStrings_Release(familyNames); + IDWriteFont_Release(font); + return family; +} + +static int map_width(enum DWRITE_FONT_STRETCH stretch) +{ + switch (stretch) { + case DWRITE_FONT_STRETCH_ULTRA_CONDENSED: return 50; + case DWRITE_FONT_STRETCH_EXTRA_CONDENSED: return 63; + case DWRITE_FONT_STRETCH_CONDENSED: return FONT_WIDTH_CONDENSED; + case DWRITE_FONT_STRETCH_SEMI_CONDENSED: return 88; + case DWRITE_FONT_STRETCH_MEDIUM: return FONT_WIDTH_NORMAL; + case DWRITE_FONT_STRETCH_SEMI_EXPANDED: return 113; + case DWRITE_FONT_STRETCH_EXPANDED: return FONT_WIDTH_EXPANDED; + case DWRITE_FONT_STRETCH_EXTRA_EXPANDED: return 150; + case DWRITE_FONT_STRETCH_ULTRA_EXPANDED: return 200; + default: + assert(0); + } +} + +/* + * Scan every system font on the current machine and add it + * to the libass lookup. Stores the FontPrivate as private data + * for later memory reading + */ +static void scan_fonts(IDWriteFactory *factory, + ASS_FontProvider *provider) +{ + HRESULT hr = S_OK; + IDWriteFontCollection *fontCollection = NULL; + IDWriteFont *font = NULL; + DWRITE_FONT_METRICS metrics; + DWRITE_FONT_STYLE style; + ASS_FontProviderMetaData meta = {0}; + hr = IDWriteFactory_GetSystemFontCollection(factory, &fontCollection, FALSE); + wchar_t temp_name[NAME_MAX_LENGTH]; + int size_needed = 0; + + if (FAILED(hr) || !fontCollection) + return; + + UINT32 familyCount = IDWriteFontCollection_GetFontFamilyCount(fontCollection); + + for (UINT32 i = 0; i < familyCount; ++i) { + IDWriteFontFamily *fontFamily = NULL; + IDWriteLocalizedStrings *familyNames = NULL; + IDWriteLocalizedStrings *fontNames = NULL; + IDWriteLocalizedStrings *psNames = NULL; + BOOL exists = FALSE; + char *psName = NULL; + + hr = IDWriteFontCollection_GetFontFamily(fontCollection, i, &fontFamily); + if (FAILED(hr)) + continue; + + UINT32 fontCount = IDWriteFontFamily_GetFontCount(fontFamily); + for (UINT32 j = 0; j < fontCount; ++j) { + hr = IDWriteFontFamily_GetFont(fontFamily, j, &font); + if (FAILED(hr)) + continue; + + // Simulations for bold or oblique are sometimes synthesized by + // DirectWrite. We are only interested in physical fonts. + if (IDWriteFont_GetSimulations(font) != 0) { + IDWriteFont_Release(font); + continue; + } + + meta.weight = IDWriteFont_GetWeight(font); + meta.width = map_width(IDWriteFont_GetStretch(font)); + IDWriteFont_GetMetrics(font, &metrics); + style = IDWriteFont_GetStyle(font); + meta.slant = (style == DWRITE_FONT_STYLE_NORMAL) ? FONT_SLANT_NONE : + (style == DWRITE_FONT_STYLE_OBLIQUE)? FONT_SLANT_OBLIQUE : + (style == DWRITE_FONT_STYLE_ITALIC) ? FONT_SLANT_ITALIC : FONT_SLANT_NONE; + + hr = IDWriteFont_GetInformationalStrings(font, + DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME, &psNames,&exists); + if (FAILED(hr)) { + IDWriteFont_Release(font); + continue; + } + + if (exists) { + hr = IDWriteLocalizedStrings_GetString(psNames, 0, temp_name, NAME_MAX_LENGTH); + if (FAILED(hr)) { + IDWriteLocalizedStrings_Release(psNames); + IDWriteFont_Release(font); + continue; + } + + temp_name[NAME_MAX_LENGTH-1] = 0; + size_needed = WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, NULL, 0,NULL, NULL); + psName = (char *) malloc(size_needed); + WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, psName, size_needed, NULL, NULL); + IDWriteLocalizedStrings_Release(psNames); + } + + hr = IDWriteFont_GetInformationalStrings(font, + DWRITE_INFORMATIONAL_STRING_FULL_NAME, &fontNames,&exists); + if (FAILED(hr)) { + IDWriteFont_Release(font); + continue; + } + + if (exists) { + meta.n_fullname = IDWriteLocalizedStrings_GetCount(fontNames); + meta.fullnames = (char **) calloc(meta.n_fullname, sizeof(char *)); + for (UINT32 k = 0; k < meta.n_fullname; ++k) { + hr = IDWriteLocalizedStrings_GetString(fontNames, k, + temp_name, + NAME_MAX_LENGTH); + if (FAILED(hr)) { + continue; + } + + temp_name[NAME_MAX_LENGTH-1] = 0; + size_needed = WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, NULL, 0, NULL, NULL); + char *mbName = (char *) malloc(size_needed); + WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, mbName, size_needed, NULL, NULL); + meta.fullnames[k] = mbName; + } + IDWriteLocalizedStrings_Release(fontNames); + } + + hr = IDWriteFont_GetInformationalStrings(font, + DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &familyNames, &exists); + if (!exists) + hr = IDWriteFontFamily_GetFamilyNames(fontFamily, &familyNames); + if (FAILED(hr)) { + IDWriteFont_Release(font); + continue; + } + + meta.n_family = IDWriteLocalizedStrings_GetCount(familyNames); + meta.families = (char **) calloc(meta.n_family, sizeof(char *)); + for (UINT32 k = 0; k < meta.n_family; ++k) { + hr = IDWriteLocalizedStrings_GetString(familyNames, k, + temp_name, + NAME_MAX_LENGTH); + if (FAILED(hr)) { + continue; + } + + temp_name[NAME_MAX_LENGTH-1] = 0; + size_needed = WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, NULL, 0,NULL, NULL); + char *mbName = (char *) malloc(size_needed); + WideCharToMultiByte(CP_UTF8, 0, temp_name, -1, mbName, size_needed, NULL, NULL); + meta.families[k] = mbName; + } + IDWriteLocalizedStrings_Release(familyNames); + + FontPrivate *font_priv = (FontPrivate *) calloc(1, sizeof(*font_priv)); + font_priv->font = font; + + ass_font_provider_add_font(provider, &meta, NULL, 0, psName, font_priv); + + for (UINT32 k = 0; k < meta.n_family; ++k) + free(meta.families[k]); + for (UINT32 k = 0; k < meta.n_fullname; ++k) + free(meta.fullnames[k]); + free(meta.fullnames); + free(meta.families); + free(psName); + } + } +} + +/* + * Called by libass when the provider should perform the + * specified task + */ +static ASS_FontProviderFuncs directwrite_callbacks = { + get_data, + check_glyph, + destroy_font, + destroy_provider, + NULL, + NULL, + get_fallback +}; + +typedef HRESULT WINAPI (*DWriteCreateFactoryFn)( + _In_ DWRITE_FACTORY_TYPE factoryType, + _In_ REFIID iid, + _Out_ IUnknown **factory +); + +/* + * Register the directwrite provider. Upon registering + * scans all system fonts. The private data for this + * provider is IDWriteFactory + * On failure returns NULL + */ +ASS_FontProvider *ass_directwrite_add_provider(ASS_Library *lib, + ASS_FontSelector *selector, + const char *config) +{ + HRESULT hr = S_OK; + IDWriteFactory *dwFactory = NULL; + ASS_FontProvider *provider = NULL; + DWriteCreateFactoryFn DWriteCreateFactoryPtr = NULL; + ProviderPrivate *priv = NULL; + + HMODULE directwrite_lib = LoadLibraryW(L"Dwrite.dll"); + if (!directwrite_lib) + goto cleanup; + + DWriteCreateFactoryPtr = (DWriteCreateFactoryFn)GetProcAddress(directwrite_lib, + "DWriteCreateFactory"); + if (!DWriteCreateFactoryPtr) + goto cleanup; + + hr = DWriteCreateFactoryPtr(DWRITE_FACTORY_TYPE_SHARED, + &IID_IDWriteFactory, + (IUnknown **) (&dwFactory)); + if (FAILED(hr) || !dwFactory) { + ass_msg(lib, MSGL_WARN, "Failed to initialize directwrite."); + dwFactory = NULL; + goto cleanup; + } + + priv = (ProviderPrivate *)calloc(sizeof(*priv), 1); + if (!priv) + goto cleanup; + + priv->directwrite_lib = directwrite_lib; + priv->factory = dwFactory; + provider = ass_font_provider_new(selector, &directwrite_callbacks, priv); + if (!provider) + goto cleanup; + + scan_fonts(dwFactory, provider); + return provider; + +cleanup: + + free(priv); + if (dwFactory) + dwFactory->lpVtbl->Release(dwFactory); + if (directwrite_lib) + FreeLibrary(directwrite_lib); + + return NULL; +} |