diff options
Diffstat (limited to 'src/gui_dwrite.cpp')
-rw-r--r-- | src/gui_dwrite.cpp | 1345 |
1 files changed, 1345 insertions, 0 deletions
diff --git a/src/gui_dwrite.cpp b/src/gui_dwrite.cpp new file mode 100644 index 0000000..8e03dff --- /dev/null +++ b/src/gui_dwrite.cpp @@ -0,0 +1,1345 @@ +/* vi:set ts=8 sts=4 sw=4 noet: */ +/* + * Author: MURAOKA Taro <koron.kaoriya@gmail.com> + * + * Contributors: + * - Ken Takata + * - Yasuhiro Matsumoto + * + * Copyright (C) 2013 MURAOKA Taro <koron.kaoriya@gmail.com> + * THIS FILE IS DISTRIBUTED UNDER THE VIM LICENSE. + */ + +#define WIN32_LEAN_AND_MEAN + +#ifndef DYNAMIC_DIRECTX +# if WINVER < 0x0600 +# error WINVER must be 0x0600 or above to use DirectWrite(DirectX) +# endif +#endif + +#include <windows.h> +#include <crtdbg.h> +#include <assert.h> +#include <math.h> +#include <d2d1.h> +#include <d2d1helper.h> + +// Disable these macros to compile with old VC and newer SDK (V8.1 or later). +#if defined(_MSC_VER) && (_MSC_VER < 1700) +# define _COM_Outptr_ __out +# define _In_reads_(s) +# define _In_reads_opt_(s) +# define _Maybenull_ +# define _Out_writes_(s) +# define _Out_writes_opt_(s) +# define _Out_writes_to_(x, y) +# define _Out_writes_to_opt_(x, y) +# define _Outptr_ +#endif + +#ifdef FEAT_DIRECTX_COLOR_EMOJI +# include <dwrite_2.h> +#else +# include <dwrite.h> +#endif + +#include "gui_dwrite.h" + +#ifdef __MINGW32__ +# define __maybenull SAL__maybenull +# define __in SAL__in +# define __out SAL__out +#endif + +#if (defined(_MSC_VER) && (_MSC_VER >= 1700)) || (__cplusplus >= 201103L) +# define FINAL final +#else +# define FINAL +#endif + +#ifdef DYNAMIC_DIRECTX +extern "C" HINSTANCE vimLoadLib(char *name); + +typedef int (WINAPI *PGETUSERDEFAULTLOCALENAME)(LPWSTR, int); +typedef HRESULT (WINAPI *PD2D1CREATEFACTORY)(D2D1_FACTORY_TYPE, + REFIID, const D2D1_FACTORY_OPTIONS *, void **); +typedef HRESULT (WINAPI *PDWRITECREATEFACTORY)(DWRITE_FACTORY_TYPE, + REFIID, IUnknown **); + +static HINSTANCE hD2D1DLL = NULL; +static HINSTANCE hDWriteDLL = NULL; + +static PGETUSERDEFAULTLOCALENAME pGetUserDefaultLocaleName = NULL; +static PD2D1CREATEFACTORY pD2D1CreateFactory = NULL; +static PDWRITECREATEFACTORY pDWriteCreateFactory = NULL; + +#define GetUserDefaultLocaleName (*pGetUserDefaultLocaleName) +#define D2D1CreateFactory (*pD2D1CreateFactory) +#define DWriteCreateFactory (*pDWriteCreateFactory) + + static void +unload(HINSTANCE &hinst) +{ + if (hinst != NULL) + { + FreeLibrary(hinst); + hinst = NULL; + } +} +#endif // DYNAMIC_DIRECTX + +template <class T> inline void SafeRelease(T **ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + + static DWRITE_PIXEL_GEOMETRY +ToPixelGeometry(int value) +{ + switch (value) + { + default: + case 0: + return DWRITE_PIXEL_GEOMETRY_FLAT; + case 1: + return DWRITE_PIXEL_GEOMETRY_RGB; + case 2: + return DWRITE_PIXEL_GEOMETRY_BGR; + } +} + + static int +ToInt(DWRITE_PIXEL_GEOMETRY value) +{ + switch (value) + { + case DWRITE_PIXEL_GEOMETRY_FLAT: + return 0; + case DWRITE_PIXEL_GEOMETRY_RGB: + return 1; + case DWRITE_PIXEL_GEOMETRY_BGR: + return 2; + default: + return -1; + } +} + + static DWRITE_RENDERING_MODE +ToRenderingMode(int value) +{ + switch (value) + { + default: + case 0: + return DWRITE_RENDERING_MODE_DEFAULT; + case 1: + return DWRITE_RENDERING_MODE_ALIASED; + case 2: + return DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + case 3: + return DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL; + case 4: + return DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL; + case 5: + return DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; + case 6: + return DWRITE_RENDERING_MODE_OUTLINE; + } +} + + static D2D1_TEXT_ANTIALIAS_MODE +ToTextAntialiasMode(int value) +{ + switch (value) + { + default: + case 0: + return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + case 1: + return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; + case 2: + return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + case 3: + return D2D1_TEXT_ANTIALIAS_MODE_ALIASED; + } +} + + static int +ToInt(DWRITE_RENDERING_MODE value) +{ + switch (value) + { + case DWRITE_RENDERING_MODE_DEFAULT: + return 0; + case DWRITE_RENDERING_MODE_ALIASED: + return 1; + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC: + return 2; + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL: + return 3; + case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL: + return 4; + case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC: + return 5; + case DWRITE_RENDERING_MODE_OUTLINE: + return 6; + default: + return -1; + } +} + +class FontCache { +public: + struct Item { + HFONT hFont; + IDWriteTextFormat* pTextFormat; + DWRITE_FONT_WEIGHT fontWeight; + DWRITE_FONT_STYLE fontStyle; + Item() : hFont(NULL), pTextFormat(NULL) {} + }; + +private: + int mSize; + Item *mItems; + +public: + FontCache(int size = 2) : + mSize(size), + mItems(new Item[size]) + { + } + + ~FontCache() + { + for (int i = 0; i < mSize; ++i) + SafeRelease(&mItems[i].pTextFormat); + delete[] mItems; + } + + bool get(HFONT hFont, Item &item) + { + int n = find(hFont); + if (n < 0) + return false; + item = mItems[n]; + slide(n); + return true; + } + + void put(const Item& item) + { + int n = find(item.hFont); + if (n < 0) + n = mSize - 1; + if (mItems[n].pTextFormat != item.pTextFormat) + { + SafeRelease(&mItems[n].pTextFormat); + item.pTextFormat->AddRef(); + } + mItems[n] = item; + slide(n); + } + +private: + int find(HFONT hFont) + { + for (int i = 0; i < mSize; ++i) + { + if (mItems[i].hFont == hFont) + return i; + } + return -1; + } + + void slide(int nextTop) + { + if (nextTop == 0) + return; + Item tmp = mItems[nextTop]; + for (int i = nextTop - 1; i >= 0; --i) + mItems[i + 1] = mItems[i]; + mItems[0] = tmp; + } +}; + +enum DrawingMode { + DM_GDI = 0, + DM_DIRECTX = 1, + DM_INTEROP = 2, +}; + +struct DWriteContext { + HDC mHDC; + RECT mBindRect; + DrawingMode mDMode; + HDC mInteropHDC; + bool mDrawing; + bool mFallbackDC; + + ID2D1Factory *mD2D1Factory; + + ID2D1DCRenderTarget *mRT; + ID2D1GdiInteropRenderTarget *mGDIRT; + ID2D1SolidColorBrush *mBrush; + ID2D1Bitmap *mBitmap; + + IDWriteFactory *mDWriteFactory; +#ifdef FEAT_DIRECTX_COLOR_EMOJI + IDWriteFactory2 *mDWriteFactory2; +#endif + + IDWriteGdiInterop *mGdiInterop; + IDWriteRenderingParams *mRenderingParams; + + FontCache mFontCache; + IDWriteTextFormat *mTextFormat; + DWRITE_FONT_WEIGHT mFontWeight; + DWRITE_FONT_STYLE mFontStyle; + + D2D1_TEXT_ANTIALIAS_MODE mTextAntialiasMode; + + // METHODS + + DWriteContext(); + + virtual ~DWriteContext(); + + HRESULT CreateDeviceResources(); + + void DiscardDeviceResources(); + + HRESULT CreateTextFormatFromLOGFONT(const LOGFONTW &logFont, + IDWriteTextFormat **ppTextFormat); + + HRESULT SetFontByLOGFONT(const LOGFONTW &logFont); + + void SetFont(HFONT hFont); + + void Rebind(); + + void BindDC(HDC hdc, const RECT *rect); + + HRESULT SetDrawingMode(DrawingMode mode); + + ID2D1Brush* SolidBrush(COLORREF color); + + void DrawText(const WCHAR *text, int len, + int x, int y, int w, int h, int cellWidth, COLORREF color, + UINT fuOptions, const RECT *lprc, const INT *lpDx); + + void FillRect(const RECT *rc, COLORREF color); + + void DrawLine(int x1, int y1, int x2, int y2, COLORREF color); + + void SetPixel(int x, int y, COLORREF color); + + void Scroll(int x, int y, const RECT *rc); + + void Flush(); + + void SetRenderingParams( + const DWriteRenderingParams *params); + + DWriteRenderingParams *GetRenderingParams( + DWriteRenderingParams *params); +}; + +class AdjustedGlyphRun : public DWRITE_GLYPH_RUN +{ +private: + FLOAT &mAccum; + FLOAT mDelta; + FLOAT *mAdjustedAdvances; + +public: + AdjustedGlyphRun( + const DWRITE_GLYPH_RUN *glyphRun, + FLOAT cellWidth, + FLOAT &accum) : + DWRITE_GLYPH_RUN(*glyphRun), + mAccum(accum), + mDelta(0.0f), + mAdjustedAdvances(new FLOAT[glyphRun->glyphCount]) + { + assert(cellWidth != 0.0f); + for (UINT32 i = 0; i < glyphRun->glyphCount; ++i) + { + FLOAT orig = glyphRun->glyphAdvances[i]; + FLOAT adjusted = adjustToCell(orig, cellWidth); + mAdjustedAdvances[i] = adjusted; + mDelta += adjusted - orig; + } + glyphAdvances = mAdjustedAdvances; + } + + ~AdjustedGlyphRun() + { + mAccum += mDelta; + delete[] mAdjustedAdvances; + } + + static FLOAT adjustToCell(FLOAT value, FLOAT cellWidth) + { + int cellCount = int(floor(value / cellWidth + 0.5f)); + if (cellCount < 1) + cellCount = 1; + return cellCount * cellWidth; + } +}; + +struct TextRendererContext { + // const fields. + COLORREF color; + FLOAT cellWidth; + + // working fields. + FLOAT offsetX; +}; + +class TextRenderer FINAL : public IDWriteTextRenderer +{ +public: + TextRenderer( + DWriteContext* pDWC) : + cRefCount_(0), + pDWC_(pDWC) + { + AddRef(); + } + + // add "virtual" to avoid a compiler warning + virtual ~TextRenderer() + { + } + + IFACEMETHOD(IsPixelSnappingDisabled)( + __maybenull void* clientDrawingContext, + __out BOOL* isDisabled) + { + *isDisabled = FALSE; + return S_OK; + } + + IFACEMETHOD(GetCurrentTransform)( + __maybenull void* clientDrawingContext, + __out DWRITE_MATRIX* transform) + { + // forward the render target's transform + pDWC_->mRT->GetTransform( + reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform)); + return S_OK; + } + + IFACEMETHOD(GetPixelsPerDip)( + __maybenull void* clientDrawingContext, + __out FLOAT* pixelsPerDip) + { + float dpiX, unused; + pDWC_->mRT->GetDpi(&dpiX, &unused); + *pixelsPerDip = dpiX / 96.0f; + return S_OK; + } + + IFACEMETHOD(DrawUnderline)( + __maybenull void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + __in DWRITE_UNDERLINE const* underline, + IUnknown* clientDrawingEffect) + { + return E_NOTIMPL; + } + + IFACEMETHOD(DrawStrikethrough)( + __maybenull void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + __in DWRITE_STRIKETHROUGH const* strikethrough, + IUnknown* clientDrawingEffect) + { + return E_NOTIMPL; + } + + IFACEMETHOD(DrawInlineObject)( + __maybenull void* clientDrawingContext, + FLOAT originX, + FLOAT originY, + IDWriteInlineObject* inlineObject, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown* clientDrawingEffect) + { + return E_NOTIMPL; + } + + IFACEMETHOD(DrawGlyphRun)( + __maybenull 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) + { + TextRendererContext *context = + reinterpret_cast<TextRendererContext*>(clientDrawingContext); + + AdjustedGlyphRun adjustedGlyphRun(glyphRun, context->cellWidth, + context->offsetX); + +#ifdef FEAT_DIRECTX_COLOR_EMOJI + if (pDWC_->mDWriteFactory2 != NULL) + { + IDWriteColorGlyphRunEnumerator *enumerator = NULL; + HRESULT hr = pDWC_->mDWriteFactory2->TranslateColorGlyphRun( + baselineOriginX + context->offsetX, + baselineOriginY, + &adjustedGlyphRun, + NULL, + DWRITE_MEASURING_MODE_GDI_NATURAL, + NULL, + 0, + &enumerator); + if (SUCCEEDED(hr)) + { + // Draw by IDWriteFactory2 for color emoji + BOOL hasRun = TRUE; + enumerator->MoveNext(&hasRun); + while (hasRun) + { + const DWRITE_COLOR_GLYPH_RUN* colorGlyphRun; + enumerator->GetCurrentRun(&colorGlyphRun); + + pDWC_->mBrush->SetColor(colorGlyphRun->runColor); + pDWC_->mRT->DrawGlyphRun( + D2D1::Point2F( + colorGlyphRun->baselineOriginX, + colorGlyphRun->baselineOriginY), + &colorGlyphRun->glyphRun, + pDWC_->mBrush, + DWRITE_MEASURING_MODE_NATURAL); + enumerator->MoveNext(&hasRun); + } + SafeRelease(&enumerator); + return S_OK; + } + } +#endif + + // Draw by IDWriteFactory (without color emoji) + pDWC_->mRT->DrawGlyphRun( + D2D1::Point2F( + baselineOriginX + context->offsetX, + baselineOriginY), + &adjustedGlyphRun, + pDWC_->SolidBrush(context->color), + DWRITE_MEASURING_MODE_NATURAL); + return S_OK; + } + +public: + IFACEMETHOD_(unsigned long, AddRef) () + { + return InterlockedIncrement(&cRefCount_); + } + + IFACEMETHOD_(unsigned long, Release) () + { + long newCount = InterlockedDecrement(&cRefCount_); + + if (newCount == 0) + { + delete this; + return 0; + } + return newCount; + } + + IFACEMETHOD(QueryInterface)( + IID const& riid, + void** ppvObject) + { + if (__uuidof(IDWriteTextRenderer) == riid) + { + *ppvObject = this; + } + else if (__uuidof(IDWritePixelSnapping) == riid) + { + *ppvObject = this; + } + else if (__uuidof(IUnknown) == riid) + { + *ppvObject = this; + } + else + { + *ppvObject = NULL; + return E_FAIL; + } + + return S_OK; + } + +private: + long cRefCount_; + DWriteContext* pDWC_; +}; + +DWriteContext::DWriteContext() : + mHDC(NULL), + mBindRect(), + mDMode(DM_GDI), + mInteropHDC(NULL), + mDrawing(false), + mFallbackDC(false), + mD2D1Factory(NULL), + mRT(NULL), + mGDIRT(NULL), + mBrush(NULL), + mBitmap(NULL), + mDWriteFactory(NULL), +#ifdef FEAT_DIRECTX_COLOR_EMOJI + mDWriteFactory2(NULL), +#endif + mGdiInterop(NULL), + mRenderingParams(NULL), + mFontCache(8), + mTextFormat(NULL), + mFontWeight(DWRITE_FONT_WEIGHT_NORMAL), + mFontStyle(DWRITE_FONT_STYLE_NORMAL), + mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT) +{ + HRESULT hr; + + hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory), NULL, + reinterpret_cast<void**>(&mD2D1Factory)); + _RPT2(_CRT_WARN, "D2D1CreateFactory: hr=%p p=%p\n", hr, mD2D1Factory); + + if (SUCCEEDED(hr)) + { + hr = DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&mDWriteFactory)); + _RPT2(_CRT_WARN, "DWriteCreateFactory: hr=%p p=%p\n", hr, + mDWriteFactory); + } + +#ifdef FEAT_DIRECTX_COLOR_EMOJI + if (SUCCEEDED(hr)) + { + DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory2), + reinterpret_cast<IUnknown**>(&mDWriteFactory2)); + _RPT1(_CRT_WARN, "IDWriteFactory2: %s\n", SUCCEEDED(hr) ? "available" : "not available"); + } +#endif + + if (SUCCEEDED(hr)) + { + hr = mDWriteFactory->GetGdiInterop(&mGdiInterop); + _RPT2(_CRT_WARN, "GetGdiInterop: hr=%p p=%p\n", hr, mGdiInterop); + } + + if (SUCCEEDED(hr)) + { + hr = mDWriteFactory->CreateRenderingParams(&mRenderingParams); + _RPT2(_CRT_WARN, "CreateRenderingParams: hr=%p p=%p\n", hr, + mRenderingParams); + } +} + +DWriteContext::~DWriteContext() +{ + SafeRelease(&mTextFormat); + SafeRelease(&mRenderingParams); + SafeRelease(&mGdiInterop); + SafeRelease(&mDWriteFactory); +#ifdef FEAT_DIRECTX_COLOR_EMOJI + SafeRelease(&mDWriteFactory2); +#endif + SafeRelease(&mBitmap); + SafeRelease(&mBrush); + SafeRelease(&mGDIRT); + SafeRelease(&mRT); + SafeRelease(&mD2D1Factory); +} + + HRESULT +DWriteContext::CreateDeviceResources() +{ + HRESULT hr; + + if (mRT != NULL) + return S_OK; + + D2D1_RENDER_TARGET_PROPERTIES props = { + D2D1_RENDER_TARGET_TYPE_DEFAULT, + { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE }, + 0, 0, + D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, + D2D1_FEATURE_LEVEL_DEFAULT + }; + hr = mD2D1Factory->CreateDCRenderTarget(&props, &mRT); + _RPT2(_CRT_WARN, "CreateDCRenderTarget: hr=%p p=%p\n", hr, mRT); + + if (SUCCEEDED(hr)) + { + // This always succeeds. + mRT->QueryInterface( + __uuidof(ID2D1GdiInteropRenderTarget), + reinterpret_cast<void**>(&mGDIRT)); + _RPT1(_CRT_WARN, "GdiInteropRenderTarget: p=%p\n", mGDIRT); + } + + if (SUCCEEDED(hr)) + { + hr = mRT->CreateSolidColorBrush( + D2D1::ColorF(D2D1::ColorF::Black), + &mBrush); + _RPT2(_CRT_WARN, "CreateSolidColorBrush: hr=%p p=%p\n", hr, mBrush); + } + + if (SUCCEEDED(hr)) + Rebind(); + + return hr; +} + + void +DWriteContext::DiscardDeviceResources() +{ + SafeRelease(&mBitmap); + SafeRelease(&mBrush); + SafeRelease(&mGDIRT); + SafeRelease(&mRT); +} + + HRESULT +DWriteContext::CreateTextFormatFromLOGFONT(const LOGFONTW &logFont, + IDWriteTextFormat **ppTextFormat) +{ + // Most of this function is copied from: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/RenderTest/TextHelpers.cpp + HRESULT hr = S_OK; + IDWriteTextFormat *pTextFormat = NULL; + + IDWriteFont *font = NULL; + IDWriteFontFamily *fontFamily = NULL; + IDWriteLocalizedStrings *localizedFamilyNames = NULL; + float fontSize = 0; + + if (SUCCEEDED(hr)) + { + hr = mGdiInterop->CreateFontFromLOGFONT(&logFont, &font); + } + + // Get the font family to which this font belongs. + if (SUCCEEDED(hr)) + { + hr = font->GetFontFamily(&fontFamily); + } + + // Get the family names. This returns an object that encapsulates one or + // more names with the same meaning but in different languages. + if (SUCCEEDED(hr)) + { + hr = fontFamily->GetFamilyNames(&localizedFamilyNames); + } + + // Get the family name at index zero. If we were going to display the name + // we'd want to try to find one that matched the use locale, but for + // purposes of creating a text format object any language will do. + + wchar_t familyName[100]; + if (SUCCEEDED(hr)) + { + hr = localizedFamilyNames->GetString(0, familyName, + ARRAYSIZE(familyName)); + } + + if (SUCCEEDED(hr)) + { + // Use lfHeight of the LOGFONT as font size. + fontSize = float(logFont.lfHeight); + + if (fontSize < 0) + { + // Negative lfHeight represents the size of the em unit. + fontSize = -fontSize; + } + else + { + // Positive lfHeight represents the cell height (ascent + + // descent). + DWRITE_FONT_METRICS fontMetrics; + font->GetMetrics(&fontMetrics); + + // Convert the cell height (ascent + descent) from design units + // to ems. + float cellHeight = static_cast<float>( + fontMetrics.ascent + fontMetrics.descent) + / fontMetrics.designUnitsPerEm; + + // Divide the font size by the cell height to get the font em + // size. + fontSize /= cellHeight; + } + } + + // The text format includes a locale name. Ideally, this would be the + // language of the text, which may or may not be the same as the primary + // language of the user. However, for our purposes the user locale will do. + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + if (SUCCEEDED(hr)) + { + if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0) + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + if (SUCCEEDED(hr)) + { + // Create the text format object. + hr = mDWriteFactory->CreateTextFormat( + familyName, + NULL, // no custom font collection + font->GetWeight(), + font->GetStyle(), + font->GetStretch(), + fontSize, + localeName, + &pTextFormat); + } + + if (SUCCEEDED(hr)) + hr = pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); + + if (SUCCEEDED(hr)) + hr = pTextFormat->SetParagraphAlignment( + DWRITE_PARAGRAPH_ALIGNMENT_CENTER); + + if (SUCCEEDED(hr)) + hr = pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); + + SafeRelease(&localizedFamilyNames); + SafeRelease(&fontFamily); + SafeRelease(&font); + + if (SUCCEEDED(hr)) + *ppTextFormat = pTextFormat; + else + SafeRelease(&pTextFormat); + + return hr; +} + + HRESULT +DWriteContext::SetFontByLOGFONT(const LOGFONTW &logFont) +{ + HRESULT hr = S_OK; + IDWriteTextFormat *pTextFormat = NULL; + + hr = CreateTextFormatFromLOGFONT(logFont, &pTextFormat); + + if (SUCCEEDED(hr)) + { + SafeRelease(&mTextFormat); + mTextFormat = pTextFormat; + mFontWeight = static_cast<DWRITE_FONT_WEIGHT>(logFont.lfWeight); + mFontStyle = logFont.lfItalic ? DWRITE_FONT_STYLE_ITALIC + : DWRITE_FONT_STYLE_NORMAL; + } + + return hr; +} + + void +DWriteContext::SetFont(HFONT hFont) +{ + FontCache::Item item; + if (mFontCache.get(hFont, item)) + { + if (item.pTextFormat != NULL) + { + item.pTextFormat->AddRef(); + SafeRelease(&mTextFormat); + mTextFormat = item.pTextFormat; + mFontWeight = item.fontWeight; + mFontStyle = item.fontStyle; + mFallbackDC = false; + } + else + mFallbackDC = true; + return; + } + + HRESULT hr = E_FAIL; + LOGFONTW lf; + if (GetObjectW(hFont, sizeof(lf), &lf)) + hr = SetFontByLOGFONT(lf); + + item.hFont = hFont; + if (SUCCEEDED(hr)) + { + item.pTextFormat = mTextFormat; + item.fontWeight = mFontWeight; + item.fontStyle = mFontStyle; + mFallbackDC = false; + } + else + mFallbackDC = true; + mFontCache.put(item); +} + + void +DWriteContext::Rebind() +{ + SafeRelease(&mBitmap); + + mRT->BindDC(mHDC, &mBindRect); + mRT->SetTransform(D2D1::IdentityMatrix()); + + D2D1_BITMAP_PROPERTIES props = { + {DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE}, + 96.0f, 96.0f + }; + mRT->CreateBitmap( + D2D1::SizeU(mBindRect.right - mBindRect.left, + mBindRect.bottom - mBindRect.top), + props, &mBitmap); +} + + void +DWriteContext::BindDC(HDC hdc, const RECT *rect) +{ + mHDC = hdc; + mBindRect = *rect; + + if (mRT == NULL) + CreateDeviceResources(); + else + { + Flush(); + Rebind(); + } +} + + HRESULT +DWriteContext::SetDrawingMode(DrawingMode mode) +{ + HRESULT hr = S_OK; + + switch (mode) + { + default: + case DM_GDI: + if (mInteropHDC != NULL) + { + mGDIRT->ReleaseDC(NULL); + mInteropHDC = NULL; + } + if (mDrawing) + { + hr = mRT->EndDraw(); + if (hr == D2DERR_RECREATE_TARGET) + { + hr = S_OK; + DiscardDeviceResources(); + CreateDeviceResources(); + } + mDrawing = false; + } + break; + + case DM_DIRECTX: + if (mInteropHDC != NULL) + { + mGDIRT->ReleaseDC(NULL); + mInteropHDC = NULL; + } + else if (mDrawing == false) + { + CreateDeviceResources(); + mRT->BeginDraw(); + mDrawing = true; + } + break; + + case DM_INTEROP: + if (mDrawing == false) + { + CreateDeviceResources(); + mRT->BeginDraw(); + mDrawing = true; + } + if (mInteropHDC == NULL) + hr = mGDIRT->GetDC(D2D1_DC_INITIALIZE_MODE_COPY, &mInteropHDC); + break; + } + mDMode = mode; + return hr; +} + + ID2D1Brush* +DWriteContext::SolidBrush(COLORREF color) +{ + mBrush->SetColor(D2D1::ColorF(UINT32(GetRValue(color)) << 16 | + UINT32(GetGValue(color)) << 8 | UINT32(GetBValue(color)))); + return mBrush; +} + + void +DWriteContext::DrawText(const WCHAR *text, int len, + int x, int y, int w, int h, int cellWidth, COLORREF color, + UINT fuOptions, const RECT *lprc, const INT *lpDx) +{ + if (mFallbackDC) + { + // Fall back to GDI rendering. + HRESULT hr = SetDrawingMode(DM_INTEROP); + if (SUCCEEDED(hr)) + { + HGDIOBJ hFont = ::GetCurrentObject(mHDC, OBJ_FONT); + HGDIOBJ hOldFont = ::SelectObject(mInteropHDC, hFont); + ::SetTextColor(mInteropHDC, color); + ::SetBkMode(mInteropHDC, ::GetBkMode(mHDC)); + ::ExtTextOutW(mInteropHDC, x, y, fuOptions, lprc, text, len, lpDx); + ::SelectObject(mInteropHDC, hOldFont); + } + return; + } + + HRESULT hr; + IDWriteTextLayout *textLayout = NULL; + + SetDrawingMode(DM_DIRECTX); + + hr = mDWriteFactory->CreateTextLayout(text, len, mTextFormat, + FLOAT(w), FLOAT(h), &textLayout); + + if (SUCCEEDED(hr)) + { + DWRITE_TEXT_RANGE textRange = { 0, UINT32(len) }; + textLayout->SetFontWeight(mFontWeight, textRange); + textLayout->SetFontStyle(mFontStyle, textRange); + + TextRenderer renderer(this); + TextRendererContext context = { color, FLOAT(cellWidth), 0.0f }; + textLayout->Draw(&context, &renderer, FLOAT(x), FLOAT(y) - 0.5f); + } + + SafeRelease(&textLayout); +} + + void +DWriteContext::FillRect(const RECT *rc, COLORREF color) +{ + if (mDMode == DM_INTEROP) + { + // GDI functions are used before this call. Keep using GDI. + // (Switching to Direct2D causes terrible slowdown.) + HBRUSH hbr = ::CreateSolidBrush(color); + ::FillRect(mInteropHDC, rc, hbr); + ::DeleteObject(HGDIOBJ(hbr)); + } + else + { + SetDrawingMode(DM_DIRECTX); + mRT->FillRectangle( + D2D1::RectF(FLOAT(rc->left), FLOAT(rc->top), + FLOAT(rc->right), FLOAT(rc->bottom)), + SolidBrush(color)); + } +} + + void +DWriteContext::DrawLine(int x1, int y1, int x2, int y2, COLORREF color) +{ + if (mDMode == DM_INTEROP) + { + // GDI functions are used before this call. Keep using GDI. + // (Switching to Direct2D causes terrible slowdown.) + HPEN hpen = ::CreatePen(PS_SOLID, 1, color); + HGDIOBJ old_pen = ::SelectObject(mInteropHDC, HGDIOBJ(hpen)); + ::MoveToEx(mInteropHDC, x1, y1, NULL); + ::LineTo(mInteropHDC, x2, y2); + ::SelectObject(mInteropHDC, old_pen); + ::DeleteObject(HGDIOBJ(hpen)); + } + else + { + SetDrawingMode(DM_DIRECTX); + mRT->DrawLine( + D2D1::Point2F(FLOAT(x1), FLOAT(y1) + 0.5f), + D2D1::Point2F(FLOAT(x2), FLOAT(y2) + 0.5f), + SolidBrush(color)); + } +} + + void +DWriteContext::SetPixel(int x, int y, COLORREF color) +{ + if (mDMode == DM_INTEROP) + { + // GDI functions are used before this call. Keep using GDI. + // (Switching to Direct2D causes terrible slowdown.) + ::SetPixel(mInteropHDC, x, y, color); + } + else + { + SetDrawingMode(DM_DIRECTX); + // Direct2D doesn't have SetPixel API. Use DrawLine instead. + mRT->DrawLine( + D2D1::Point2F(FLOAT(x), FLOAT(y) + 0.5f), + D2D1::Point2F(FLOAT(x+1), FLOAT(y) + 0.5f), + SolidBrush(color)); + } +} + + void +DWriteContext::Scroll(int x, int y, const RECT *rc) +{ + SetDrawingMode(DM_DIRECTX); + mRT->Flush(); + + D2D1_RECT_U srcRect; + D2D1_POINT_2U destPoint; + if (x >= 0) + { + srcRect.left = rc->left; + srcRect.right = rc->right - x; + destPoint.x = rc->left + x; + } + else + { + srcRect.left = rc->left - x; + srcRect.right = rc->right; + destPoint.x = rc->left; + } + if (y >= 0) + { + srcRect.top = rc->top; + srcRect.bottom = rc->bottom - y; + destPoint.y = rc->top + y; + } + else + { + srcRect.top = rc->top - y; + srcRect.bottom = rc->bottom; + destPoint.y = rc->top; + } + mBitmap->CopyFromRenderTarget(&destPoint, mRT, &srcRect); + + D2D1_RECT_F destRect = { + FLOAT(destPoint.x), FLOAT(destPoint.y), + FLOAT(destPoint.x + srcRect.right - srcRect.left), + FLOAT(destPoint.y + srcRect.bottom - srcRect.top) + }; + mRT->DrawBitmap(mBitmap, destRect, 1.0F, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, destRect); +} + + void +DWriteContext::Flush() +{ + SetDrawingMode(DM_GDI); +} + + void +DWriteContext::SetRenderingParams( + const DWriteRenderingParams *params) +{ + if (mDWriteFactory == NULL) + return; + + IDWriteRenderingParams *renderingParams = NULL; + D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode = + D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + HRESULT hr; + if (params != NULL) + { + hr = mDWriteFactory->CreateCustomRenderingParams(params->gamma, + params->enhancedContrast, params->clearTypeLevel, + ToPixelGeometry(params->pixelGeometry), + ToRenderingMode(params->renderingMode), &renderingParams); + textAntialiasMode = ToTextAntialiasMode(params->textAntialiasMode); + } + else + hr = mDWriteFactory->CreateRenderingParams(&renderingParams); + if (SUCCEEDED(hr) && renderingParams != NULL) + { + SafeRelease(&mRenderingParams); + mRenderingParams = renderingParams; + mTextAntialiasMode = textAntialiasMode; + + Flush(); + mRT->SetTextRenderingParams(mRenderingParams); + mRT->SetTextAntialiasMode(mTextAntialiasMode); + } +} + + DWriteRenderingParams * +DWriteContext::GetRenderingParams( + DWriteRenderingParams *params) +{ + if (params != NULL && mRenderingParams != NULL) + { + params->gamma = mRenderingParams->GetGamma(); + params->enhancedContrast = mRenderingParams->GetEnhancedContrast(); + params->clearTypeLevel = mRenderingParams->GetClearTypeLevel(); + params->pixelGeometry = ToInt(mRenderingParams->GetPixelGeometry()); + params->renderingMode = ToInt(mRenderingParams->GetRenderingMode()); + params->textAntialiasMode = mTextAntialiasMode; + } + return params; +} + +//////////////////////////////////////////////////////////////////////////// +// PUBLIC C INTERFACES + + void +DWrite_Init(void) +{ +#ifdef DYNAMIC_DIRECTX + // Load libraries. + hD2D1DLL = vimLoadLib(const_cast<char*>("d2d1.dll")); + hDWriteDLL = vimLoadLib(const_cast<char*>("dwrite.dll")); + if (hD2D1DLL == NULL || hDWriteDLL == NULL) + { + DWrite_Final(); + return; + } + // Get address of procedures. + pGetUserDefaultLocaleName = (PGETUSERDEFAULTLOCALENAME)GetProcAddress( + GetModuleHandle("kernel32.dll"), "GetUserDefaultLocaleName"); + pD2D1CreateFactory = (PD2D1CREATEFACTORY)GetProcAddress(hD2D1DLL, + "D2D1CreateFactory"); + pDWriteCreateFactory = (PDWRITECREATEFACTORY)GetProcAddress(hDWriteDLL, + "DWriteCreateFactory"); +#endif +} + + void +DWrite_Final(void) +{ +#ifdef DYNAMIC_DIRECTX + pGetUserDefaultLocaleName = NULL; + pD2D1CreateFactory = NULL; + pDWriteCreateFactory = NULL; + unload(hDWriteDLL); + unload(hD2D1DLL); +#endif +} + + DWriteContext * +DWriteContext_Open(void) +{ +#ifdef DYNAMIC_DIRECTX + if (pGetUserDefaultLocaleName == NULL || pD2D1CreateFactory == NULL + || pDWriteCreateFactory == NULL) + return NULL; +#endif + return new DWriteContext(); +} + + void +DWriteContext_BindDC(DWriteContext *ctx, HDC hdc, const RECT *rect) +{ + if (ctx != NULL) + ctx->BindDC(hdc, rect); +} + + void +DWriteContext_SetFont(DWriteContext *ctx, HFONT hFont) +{ + if (ctx != NULL) + ctx->SetFont(hFont); +} + + void +DWriteContext_DrawText( + DWriteContext *ctx, + const WCHAR *text, + int len, + int x, + int y, + int w, + int h, + int cellWidth, + COLORREF color, + UINT fuOptions, + const RECT *lprc, + const INT *lpDx) +{ + if (ctx != NULL) + ctx->DrawText(text, len, x, y, w, h, cellWidth, color, + fuOptions, lprc, lpDx); +} + + void +DWriteContext_FillRect(DWriteContext *ctx, const RECT *rc, COLORREF color) +{ + if (ctx != NULL) + ctx->FillRect(rc, color); +} + + void +DWriteContext_DrawLine(DWriteContext *ctx, int x1, int y1, int x2, int y2, + COLORREF color) +{ + if (ctx != NULL) + ctx->DrawLine(x1, y1, x2, y2, color); +} + + void +DWriteContext_SetPixel(DWriteContext *ctx, int x, int y, COLORREF color) +{ + if (ctx != NULL) + ctx->SetPixel(x, y, color); +} + + void +DWriteContext_Scroll(DWriteContext *ctx, int x, int y, const RECT *rc) +{ + if (ctx != NULL) + ctx->Scroll(x, y, rc); +} + + void +DWriteContext_Flush(DWriteContext *ctx) +{ + if (ctx != NULL) + ctx->Flush(); +} + + void +DWriteContext_Close(DWriteContext *ctx) +{ + delete ctx; +} + + void +DWriteContext_SetRenderingParams( + DWriteContext *ctx, + const DWriteRenderingParams *params) +{ + if (ctx != NULL) + ctx->SetRenderingParams(params); +} + + DWriteRenderingParams * +DWriteContext_GetRenderingParams( + DWriteContext *ctx, + DWriteRenderingParams *params) +{ + if (ctx != NULL) + return ctx->GetRenderingParams(params); + else + return NULL; +} |