diff options
author | Bram Moolenaar <Bram@vim.org> | 2014-08-06 14:52:30 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2014-08-06 14:52:30 +0200 |
commit | b5a7a8b5451c6fe8a2cc1d5d86c42d9b9a50ef98 (patch) | |
tree | f5616b152c67bfa1de710ca93fe6f883c9069b46 | |
parent | 8c1329cb59e964797b8abdcf8d42af9877ea8daa (diff) |
updated for version 7.4.393v7.4.393
Problem: Text drawing on newer MS-Windows systems is suboptimal. Some
multi-byte characters are not displayed, even though the same font
in Notepad can display them. (Srinath Avadhanula)
Solution: Add the 'renderoptions' option to enable Direct-X drawing. (Taro
Muraoka)
-rw-r--r-- | runtime/doc/eval.txt | 1 | ||||
-rw-r--r-- | runtime/doc/options.txt | 71 | ||||
-rw-r--r-- | runtime/doc/various.txt | 1 | ||||
-rw-r--r-- | src/Make_cyg.mak | 32 | ||||
-rw-r--r-- | src/Make_ming.mak | 35 | ||||
-rw-r--r-- | src/Make_mvc.mak | 21 | ||||
-rw-r--r-- | src/eval.c | 3 | ||||
-rw-r--r-- | src/gui_dwrite.cpp | 901 | ||||
-rw-r--r-- | src/gui_dwrite.h | 85 | ||||
-rw-r--r-- | src/gui_w32.c | 204 | ||||
-rw-r--r-- | src/gui_w48.c | 21 | ||||
-rw-r--r-- | src/option.c | 17 | ||||
-rw-r--r-- | src/option.h | 3 | ||||
-rw-r--r-- | src/proto/gui_w32.pro | 2 | ||||
-rw-r--r-- | src/version.c | 9 | ||||
-rw-r--r-- | src/vim.h | 7 |
16 files changed, 1390 insertions, 23 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cbd995da3d..62aa167041 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6622,6 +6622,7 @@ dialog_con Compiled with console dialog support. dialog_gui Compiled with GUI dialog support. diff Compiled with |vimdiff| and 'diff' support. digraphs Compiled with support for digraphs. +directx Compiled with support for Direct-X and 'renderoptions'. dnd Compiled with support for the "~ register |quote_~|. dos16 16 bits DOS version of Vim. dos32 32 bits DOS (DJGPP) version of Vim. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 90d4ca7a70..5f4a06e7ef 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5650,6 +5650,77 @@ A jump table for the options with a short description can be found at |Q_op|. this option at the default "on". Only switch it off when working with old Vi scripts. + *'renderoptions'* *'rop'* +'renderoptions' 'rop' string (default: empty) + global + {not in Vi} + {only available when compiled with GUI and DIRECTX on + MS-Windows} + Select a text renderer and set its options. The options depend on the + renderer. + + Syntax: > + set rop=type:{renderer}(,{name}:{value})* +< + Currently, only one optional renderer is available. + + render behavior ~ + directx Vim will draw text using DirectX (DirectWrite). It makes + drawn glyphs more beautiful than default GDI. + It requires 'encoding' is "utf-8", and only works on + MS-Windows Vista or newer version. + + Options: + name meaning type value ~ + gamma gamma float 1.0 - 2.2 (maybe) + contrast enhancedContrast float (unknown) + level clearTypeLevel float (unknown) + geom pixelGeometry int 0 - 2 (see below) + renmode renderingMode int 0 - 6 (see below) + taamode textAntialiasMode int 0 - 3 (see below) + + See this URL for detail: + http://msdn.microsoft.com/en-us/library/dd368190.aspx + + For geom: structure of a device pixel. + 0 - DWRITE_PIXEL_GEOMETRY_FLAT + 1 - DWRITE_PIXEL_GEOMETRY_RGB + 2 - DWRITE_PIXEL_GEOMETRY_BGR + + See this URL for detail: + http://msdn.microsoft.com/en-us/library/dd368114.aspx + + For renmode: method of rendering glyphs. + 0 - DWRITE_RENDERING_MODE_DEFAULT + 1 - DWRITE_RENDERING_MODE_ALIASED + 2 - DWRITE_RENDERING_MODE_GDI_CLASSIC + 3 - DWRITE_RENDERING_MODE_GDI_NATURAL + 4 - DWRITE_RENDERING_MODE_NATURAL + 5 - DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC + 6 - DWRITE_RENDERING_MODE_OUTLINE + + See this URL for detail: + http://msdn.microsoft.com/en-us/library/dd368118.aspx + + For taamode: antialiasing mode used for drawing text. + 0 - D2D1_TEXT_ANTIALIAS_MODE_DEFAULT + 1 - D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE + 2 - D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE + 3 - D2D1_TEXT_ANTIALIAS_MODE_ALIASED + + See this URL for detail: + http://msdn.microsoft.com/en-us/library/dd368170.aspx + + Example: > + set encoding=utf-8 + set gfn=Ricty_Diminished:h12:cSHIFTJIS + set rop=type:directx +< + If select a raster font (Courier, Terminal or FixedSys) to + 'guifont', it fallbacks to be drawn by GDI automatically. + + Other render types are currently not supported. + *'report'* 'report' number (default 2) global diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index b264e5be8c..a03a0bd94a 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -337,6 +337,7 @@ N *+dialog_con* Support for |:confirm| with console dialog. N *+dialog_con_gui* Support for |:confirm| with GUI and console dialog. N *+diff* |vimdiff| and 'diff' N *+digraphs* |digraphs| *E196* +m *+directx* Win32 GUI only: DirectX and |'renderoptions'| *+dnd* Support for DnD into the "~ register |quote_~|. B *+emacs_tags* |emacs-tags| files N *+eval* expression evaluation |eval.txt| diff --git a/src/Make_cyg.mak b/src/Make_cyg.mak index f349798078..24f19c9f2a 100644 --- a/src/Make_cyg.mak +++ b/src/Make_cyg.mak @@ -8,6 +8,7 @@ # Cygwin application use the Makefile (just like on Unix). # # GUI no or yes: set to yes if you want the GUI version (yes) +# DIRECTX no or yes: set to yes if you want use DirectWrite (no) # PERL define to path to Perl dir to get Perl support (not defined) # PERL_VER define to version of Perl being used (56) # DYNAMIC_PERL no or yes: set to yes to load the Perl DLL dynamically (yes) @@ -88,6 +89,10 @@ ifndef ARCH ARCH = i386 endif +ifndef DIRECTX +DIRECTX = no +endif + ifndef WINVER WINVER = 0x0500 endif @@ -470,6 +475,15 @@ endif endif ############################## +ifeq (yes, $(DIRECTX)) +# Only allow DIRECTX for a GUI build. +DEFINES += -DFEAT_DIRECTX -DDYNAMIC_DIRECTX +EXTRA_OBJS += $(OUTDIR)/gui_dwrite.o +EXTRA_LIBS += -ld2d1 -ldwrite +USE_STDCPLUS = yes +endif + +############################## ifdef XPM # Only allow XPM for a GUI build. DEFINES += -DFEAT_XPM_W32 @@ -495,11 +509,7 @@ ifeq (yes, $(OLE)) DEFINES += -DFEAT_OLE EXTRA_OBJS += $(OUTDIR)/if_ole.o EXTRA_LIBS += -loleaut32 -ifeq (yes, $(STATIC_STDCPLUS)) -EXTRA_LIBS += -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic -else -EXTRA_LIBS += -lstdc++ -endif +USE_STDCPLUS = yes endif ############################## @@ -513,6 +523,15 @@ MKDIR = mkdir DIRSLASH = \\ endif +############################## +ifeq (yes, $(USE_STDCPLUS)) +ifeq (yes, $(STATIC_STDCPLUS)) +EXTRA_LIBS += -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic +else +EXTRA_LIBS += -lstdc++ +endif +endif + #>>>>> end of choices ########################################################################### @@ -643,6 +662,9 @@ $(OUTDIR)/ex_eval.o: ex_eval.c $(INCL) ex_cmds.h $(OUTDIR)/gui_w32.o: gui_w32.c gui_w48.c $(INCL) $(CC) -c $(CFLAGS) gui_w32.c -o $(OUTDIR)/gui_w32.o +$(OUTDIR)/gui_dwrite.o: gui_dwrite.cpp $(INCL) gui_dwrite.h + $(CC) -c $(CFLAGS) gui_dwrite.cpp -o $(OUTDIR)/gui_dwrite.o + $(OUTDIR)/if_cscope.o: if_cscope.c $(INCL) if_cscope.h $(CC) -c $(CFLAGS) if_cscope.c -o $(OUTDIR)/if_cscope.o diff --git a/src/Make_ming.mak b/src/Make_ming.mak index f7088ad58e..de1d88fe4c 100644 --- a/src/Make_ming.mak +++ b/src/Make_ming.mak @@ -31,6 +31,8 @@ DEBUG=no OPTIMIZE=MAXSPEED # set to yes to make gvim, no for vim GUI=yes +# set to yes if you want to use DirectWrite (DirectX) +DIRECTX=no # FEATURES=[TINY | SMALL | NORMAL | BIG | HUGE] # Set to TINY to make minimal version (few features). FEATURES=BIG @@ -456,6 +458,14 @@ endif endif endif +# DirectWrite (DirectX) +ifeq ($(DIRECTX),yes) +# Only allow DirectWrite for a GUI build. +ifeq (yes, $(GUI)) +DEFINES += -DFEAT_DIRECTX -DDYNAMIC_DIRECTX +endif +endif + # Only allow XPM for a GUI build. ifeq (yes, $(GUI)) @@ -593,6 +603,14 @@ OBJ += $(OUTDIR)/netbeans.o LIB += -lwsock32 endif endif +ifeq ($(DIRECTX),yes) +# Only allow DIRECTX for a GUI build. +ifeq (yes, $(GUI)) +OBJ += $(OUTDIR)/gui_dwrite.o +LIB += -ld2d1 -ldwrite +USE_STDCPLUS = yes +endif +endif ifdef XPM # Only allow XPM for a GUI build. ifeq (yes, $(GUI)) @@ -650,11 +668,7 @@ endif ifeq (yes, $(OLE)) LIB += -loleaut32 OBJ += $(OUTDIR)/if_ole.o -ifeq (yes, $(STATIC_STDCPLUS)) -LIB += -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic -else -LIB += -lstdc++ -endif +USE_STDCPLUS = yes endif ifeq (yes, $(MBYTE)) @@ -678,6 +692,14 @@ endif DEFINES+=-DDYNAMIC_ICONV endif +ifeq (yes, $(USE_STDCPLUS)) +ifeq (yes, $(STATIC_STDCPLUS)) +LIB += -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic +else +LIB += -lstdc++ +endif +endif + all: $(TARGET) vimrun.exe xxd/xxd.exe install.exe uninstal.exe GvimExt/gvimext.dll vimrun.exe: vimrun.c @@ -751,6 +773,9 @@ $(OUTDIR)/ex_eval.o: ex_eval.c $(INCL) ex_cmds.h $(OUTDIR)/gui_w32.o: gui_w32.c gui_w48.c $(INCL) $(CC) -c $(CFLAGS) gui_w32.c -o $(OUTDIR)/gui_w32.o +$(OUTDIR)/gui_dwrite.o: gui_dwrite.cpp $(INCL) gui_dwrite.h + $(CC) -c $(CFLAGS) gui_dwrite.cpp -o $(OUTDIR)/gui_dwrite.o + $(OUTDIR)/if_cscope.o: if_cscope.c $(INCL) if_cscope.h $(CC) -c $(CFLAGS) if_cscope.c -o $(OUTDIR)/if_cscope.o diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 4a979e419b..24069b24e9 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -24,6 +24,9 @@ # # GUI interface: GUI=yes (default is no) # +# GUI with DirectWrite(DirectX): DIRECTX=yes +# (default is no, requires GUI=yes) +# # OLE interface: OLE=yes (usually with GUI=yes) # # Multibyte support: MBYTE=yes (default is no) @@ -168,6 +171,9 @@ OBJDIR = .\ObjG !else OBJDIR = .\ObjC !endif +!if "$(DIRECTX)" == "yes" +OBJDIR = $(OBJDIR)X +!endif !if "$(OLE)" == "yes" OBJDIR = $(OBJDIR)O !endif @@ -292,6 +298,13 @@ NBDEBUG_SRC = nbdebug.c NETBEANS_LIB = WSock32.lib !endif +# DirectWrite(DirectX) +!if "$(DIRECTX)" == "yes" +DIRECTX_DEFS = -DFEAT_DIRECTX -DDYNAMIC_DIRECTX +DIRECTX_INCL = gui_dwrite.h +DIRECTX_OBJ = $(OUTDIR)\gui_dwrite.obj +!endif + !ifndef XPM # XPM is not set, use the included xpm files, depending on the architecture. !if "$(CPU)" == "AMD64" @@ -642,6 +655,12 @@ GUI_LIB = \ SUBSYSTEM = console !endif +!if "$(GUI)" == "yes" && "$(DIRECTX)" == "yes" +CFLAGS = $(CFLAGS) $(DIRECTX_DEFS) +GUI_INCL = $(GUI_INCL) $(DIRECTX_INCL) +GUI_OBJ = $(GUI_OBJ) $(DIRECTX_OBJ) +!endif + # iconv.dll library (dynamically loaded) !ifndef ICONV ICONV = yes @@ -1107,6 +1126,8 @@ $(OUTDIR)/gui_beval.obj: $(OUTDIR) gui_beval.c $(INCL) $(GUI_INCL) $(OUTDIR)/gui_w32.obj: $(OUTDIR) gui_w32.c gui_w48.c $(INCL) $(GUI_INCL) +$(OUTDIR)/gui_dwrite.obj: $(OUTDIR) gui_dwrite.cpp $(INCL) $(GUI_INCL) + $(OUTDIR)/if_cscope.obj: $(OUTDIR) if_cscope.c $(INCL) $(OUTDIR)/if_lua.obj: $(OUTDIR) if_lua.c $(INCL) diff --git a/src/eval.c b/src/eval.c index 4e512af648..ae8331d5aa 100644 --- a/src/eval.c +++ b/src/eval.c @@ -12464,6 +12464,9 @@ f_has(argvars, rettv) #ifdef FEAT_DIGRAPHS "digraphs", #endif +#ifdef FEAT_DIRECTX + "directx", +#endif #ifdef FEAT_DND "dnd", #endif diff --git a/src/gui_dwrite.cpp b/src/gui_dwrite.cpp new file mode 100644 index 0000000000..cf83c05044 --- /dev/null +++ b/src/gui_dwrite.cpp @@ -0,0 +1,901 @@ +/* vi:set ts=8 sts=4 sw=4 noet: */ +/* + * Author: MURAOKA Taro <koron.kaoriya@gmail.com> + * + * Contributors: + * - Ken Takata + * + * 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> +#include <dwrite.h> + +#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 <class T> 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<GdiTextRendererContext*>(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: + unsigned 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<void**>(&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<IUnknown**>(&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<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, + &mTextFormat); + } + + if (SUCCEEDED(hr)) + { + mFontWeight = static_cast<DWRITE_FONT_WEIGHT>(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, 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-&g |