/* vi:set ts=8 sts=4 sw=4 noet: */ /* * Author: MURAOKA Taro * * Contributors: * - Ken Takata * * Copyright (C) 2013 MURAOKA Taro * 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 #include #include #include #include #include #include #include "gui_dwrite.h" #ifdef __MINGW32__ # define __maybenull SAL__maybenull # define __in SAL__in # define __out SAL__out #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 inline void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } struct GdiTextRendererContext { // const fields. COLORREF color; FLOAT cellWidth; // working fields. FLOAT offsetX; }; 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 AdjustedGlyphRun : public DWRITE_GLYPH_RUN { private: FLOAT mDelta; FLOAT *mAdjustedAdvances; public: AdjustedGlyphRun( const DWRITE_GLYPH_RUN *glyphRun, FLOAT cellWidth) : DWRITE_GLYPH_RUN(*glyphRun), 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(void) { delete[] mAdjustedAdvances; } FLOAT getDelta(void) const { return mDelta; } static FLOAT adjustToCell(FLOAT value, FLOAT cellWidth) { int cellCount = (int)floor(value / cellWidth + 0.5f); if (cellCount < 1) cellCount = 1; return cellCount * cellWidth; } }; class GdiTextRenderer : public IDWriteTextRenderer { public: GdiTextRenderer( IDWriteBitmapRenderTarget* bitmapRenderTarget, IDWriteRenderingParams* renderingParams) : cRefCount_(0), pRenderTarget_(bitmapRenderTarget), pRenderingParams_(renderingParams) { pRenderTarget_->AddRef(); pRenderingParams_->AddRef(); AddRef(); } ~GdiTextRenderer() { SafeRelease(&pRenderTarget_); SafeRelease(&pRenderingParams_); } 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 pRenderTarget_->GetCurrentTransform(transform); return S_OK; } IFACEMETHOD(GetPixelsPerDip)( __maybenull void* clientDrawingContext, __out FLOAT* pixelsPerDip) { *pixelsPerDip = pRenderTarget_->GetPixelsPerDip(); return S_OK; } 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) { HRESULT hr = S_OK; GdiTextRendererContext *context = reinterpret_cast(clientDrawingContext); AdjustedGlyphRun adjustedGlyphRun(glyphRun, context->cellWidth); // Pass on the drawing call to the render target to do the real work. RECT dirtyRect = {0}; hr = pRenderTarget_->DrawGlyphRun( baselineOriginX + context->offsetX, baselineOriginY, measuringMode, &adjustedGlyphRun, pRenderingParams_, context->color, &dirtyRect); context->offsetX += adjustedGlyphRun.getDelta(); return hr; } 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; } 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_; IDWriteBitmapRenderTarget* pRenderTarget_; IDWriteRenderingParams* pRenderingParams_; }; struct DWriteContext { FLOAT mDpiScaleX; FLOAT mDpiScaleY; bool mDrawing; ID2D1Factory *mD2D1Factory; ID2D1DCRenderTarget *mRT; ID2D1SolidColorBrush *mBrush; IDWriteFactory *mDWriteFactory; IDWriteGdiInterop *mGdiInterop; IDWriteRenderingParams *mRenderingParams; IDWriteTextFormat *mTextFormat; HFONT mLastHFont; DWRITE_FONT_WEIGHT mFontWeight; DWRITE_FONT_STYLE mFontStyle; D2D1_TEXT_ANTIALIAS_MODE mTextAntialiasMode; // METHODS DWriteContext(); virtual ~DWriteContext(); HRESULT SetLOGFONT(const LOGFONTW &logFont, float fontSize); void SetFont(HFONT hFont); void SetFont(const LOGFONTW &logFont); void DrawText(HDC hdc, const WCHAR* text, int len, int x, int y, int w, int h, int cellWidth, COLORREF color); float PixelsToDipsX(int x); float PixelsToDipsY(int y); void SetRenderingParams( const DWriteRenderingParams *params); DWriteRenderingParams *GetRenderingParams( DWriteRenderingParams *params); }; DWriteContext::DWriteContext() : mDpiScaleX(1.f), mDpiScaleY(1.f), mDrawing(false), mD2D1Factory(NULL), mRT(NULL), mBrush(NULL), mDWriteFactory(NULL), mGdiInterop(NULL), mRenderingParams(NULL), mTextFormat(NULL), mLastHFont(NULL), mFontWeight(DWRITE_FONT_WEIGHT_NORMAL), mFontStyle(DWRITE_FONT_STYLE_NORMAL), mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT) { HRESULT hr; HDC screen = ::GetDC(0); mDpiScaleX = ::GetDeviceCaps(screen, LOGPIXELSX) / 96.0f; mDpiScaleY = ::GetDeviceCaps(screen, LOGPIXELSY) / 96.0f; ::ReleaseDC(0, screen); hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), NULL, reinterpret_cast(&mD2D1Factory)); _RPT2(_CRT_WARN, "D2D1CreateFactory: hr=%p p=%p\n", hr, mD2D1Factory); if (SUCCEEDED(hr)) { D2D1_RENDER_TARGET_PROPERTIES props = { D2D1_RENDER_TARGET_TYPE_DEFAULT, { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE }, 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT }; hr = mD2D1Factory->CreateDCRenderTarget(&props, &mRT); _RPT2(_CRT_WARN, "CreateDCRenderTarget: hr=%p p=%p\n", hr, mRT); } 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)) { hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&mDWriteFactory)); _RPT2(_CRT_WARN, "DWriteCreateFactory: hr=%p p=%p\n", hr, mDWriteFactory); } 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); SafeRelease(&mBrush); SafeRelease(&mRT); SafeRelease(&mD2D1Factory); } HRESULT DWriteContext::SetLOGFONT(const LOGFONTW &logFont, float fontSize) { // Most of this function is copy from: http://msdn.microsoft.com/en-us/library/windows/desktop/dd941783(v=vs.85).aspx HRESULT hr = S_OK; IDWriteFont *font = NULL; IDWriteFontFamily *fontFamily = NULL; IDWriteLocalizedStrings *localizedFamilyNames = NULL; 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)) { // If no font size was passed in use the lfHeight of the LOGFONT. if (fontSize == 0) { // Convert from pixels to DIPs. fontSize = PixelsToDipsY(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( 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, &mTextFormat); } if (SUCCEEDED(hr)) { mFontWeight = static_cast(logFont.lfWeight); mFontStyle = logFont.lfItalic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; } SafeRelease(&localizedFamilyNames); SafeRelease(&fontFamily); SafeRelease(&font); return hr; } void DWriteContext::SetFont(HFONT hFont) { if (mLastHFont != hFont) { LOGFONTW lf; if (GetObjectW(hFont, sizeof(lf), &lf)) { SetFont(lf); mLastHFont = hFont; } } } void DWriteContext::SetFont(const LOGFONTW &logFont) { SafeRelease(&mTextFormat); mLastHFont = NULL; HRESULT hr = SetLOGFONT(logFont, 0.f); if (SUCCEEDED(hr)) hr = mTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); if (SUCCEEDED(hr)) hr = mTextFormat->SetParagraphAlignment( DWRITE_PARAGRAPH_ALIGNMENT_CENTER); if (SUCCEEDED(hr)) hr = mTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); } void DWriteContext::DrawText(HDC hdc, const WCHAR* text, int len, int x, int y, int w, int h, int cellWidth, COLORREF color) { HRESULT hr = S_OK; IDWriteBitmapRenderTarget *bmpRT = NULL; // Skip when any fonts are not set. if (mTextFormat == NULL) return; // Check possibility of zero divided error. if (cellWidth == 0 || mDpiScaleX == 0.0f || mDpiScaleY == 0.0f) return; if (SUCCEEDED(hr)) hr = mGdiInterop->CreateBitmapRenderTarget(hdc, w, h, &bmpRT); if (SUCCEEDED(hr)) { IDWriteTextLayout *textLayout = NULL; HDC memdc = bmpRT->GetMemoryDC(); BitBlt(memdc, 0, 0, w, h, hdc, x, y, SRCCOPY); hr = mDWriteFactory->CreateGdiCompatibleTextLayout( text, len, mTextFormat, PixelsToDipsX(w), PixelsToDipsY(h), mDpiScaleX, NULL, TRUE, &textLayout); if (SUCCEEDED(hr)) { DWRITE_TEXT_RANGE textRange = { 0, (UINT32)len }; textLayout->SetFontWeight(mFontWeight, textRange); textLayout->SetFontStyle(mFontStyle, textRange); } if (SUCCEEDED(hr)) { GdiTextRenderer *renderer = new GdiTextRenderer(bmpRT, mRenderingParams); GdiTextRendererContext data = { color, PixelsToDipsX(cellWidth), 0.0f }; textLayout->Draw(&data, renderer, 0, 0); SafeRelease(&renderer); } BitBlt(hdc, x, y, w, h, memdc, 0, 0, SRCCOPY); SafeRelease(&textLayout); } SafeRelease(&bmpRT); } float DWriteContext::PixelsToDipsX(int x) { return x / mDpiScaleX; } float DWriteContext::PixelsToDipsY(int y) { return y / mDpiScaleY; } 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; } } 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("d2d1.dll")); hDWriteDLL = vimLoadLib(const_cast("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_BeginDraw(DWriteContext *ctx) { if (ctx != NULL && ctx->mRT != NULL) { ctx->mRT->BeginDraw(); ctx->mRT->SetTransform(D2D1::IdentityMatrix()); ctx->mDrawing = true; } } void DWriteContext_BindDC(DWriteContext *ctx, HDC hdc, RECT *rect) { if (ctx != NULL && ctx->mRT != NULL) { ctx->mRT->BindDC(hdc, rect); ctx->mRT->SetTextAntialiasMode(ctx->mTextAntialiasMode); } } void DWriteContext_SetFont(DWriteContext *ctx, HFONT hFont) { if (ctx != NULL) { ctx->SetFont(hFont); } } void DWriteContext_DrawText( DWriteContext *ctx, HDC hdc, const WCHAR* text, int len, int x, int y, int w, int h, int cellWidth, COLORREF color) { if (ctx != NULL) ctx->DrawText(hdc, text, len, x, y, w, h, cellWidth, color); } void DWriteContext_EndDraw(DWriteContext *ctx) { if (ctx != NULL && ctx->mRT != NULL) { ctx->mRT->EndDraw(); ctx->mDrawing = false; } } 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; }