xref: /vim-8.2.3635/src/gui_dwrite.cpp (revision e828b762)
1 /* vi:set ts=8 sts=4 sw=4 noet: */
2 /*
3  * Author: MURAOKA Taro <[email protected]>
4  *
5  * Contributors:
6  *  - Ken Takata
7  *  - Yasuhiro Matsumoto
8  *
9  * Copyright (C) 2013 MURAOKA Taro <[email protected]>
10  * THIS FILE IS DISTRIBUTED UNDER THE VIM LICENSE.
11  */
12 
13 #define WIN32_LEAN_AND_MEAN
14 
15 #ifndef DYNAMIC_DIRECTX
16 # if WINVER < 0x0600
17 #  error WINVER must be 0x0600 or above to use DirectWrite(DirectX)
18 # endif
19 #endif
20 
21 #include <windows.h>
22 #include <crtdbg.h>
23 #include <assert.h>
24 #include <math.h>
25 #include <d2d1.h>
26 #include <d2d1helper.h>
27 
28 // Disable these macros to compile with old VC and newer SDK (V8.1 or later).
29 #if defined(_MSC_VER) && (_MSC_VER < 1700)
30 # define _COM_Outptr_ __out
31 # define _In_reads_(s)
32 # define _In_reads_opt_(s)
33 # define _Maybenull_
34 # define _Out_writes_(s)
35 # define _Out_writes_opt_(s)
36 # define _Out_writes_to_(x, y)
37 # define _Out_writes_to_opt_(x, y)
38 # define _Outptr_
39 #endif
40 
41 #ifdef FEAT_DIRECTX_COLOR_EMOJI
42 # include <dwrite_2.h>
43 #else
44 # include <dwrite.h>
45 #endif
46 
47 #include "gui_dwrite.h"
48 
49 #ifdef __MINGW32__
50 # define __maybenull	SAL__maybenull
51 # define __in		SAL__in
52 # define __out		SAL__out
53 #endif
54 
55 #if (defined(_MSC_VER) && (_MSC_VER >= 1700)) || (__cplusplus >= 201103L)
56 # define FINAL final
57 #else
58 # define FINAL
59 #endif
60 
61 #ifdef DYNAMIC_DIRECTX
62 extern "C" HINSTANCE vimLoadLib(char *name);
63 
64 typedef int (WINAPI *PGETUSERDEFAULTLOCALENAME)(LPWSTR, int);
65 typedef HRESULT (WINAPI *PD2D1CREATEFACTORY)(D2D1_FACTORY_TYPE,
66 	REFIID, const D2D1_FACTORY_OPTIONS *, void **);
67 typedef HRESULT (WINAPI *PDWRITECREATEFACTORY)(DWRITE_FACTORY_TYPE,
68 	REFIID, IUnknown **);
69 
70 static HINSTANCE hD2D1DLL = NULL;
71 static HINSTANCE hDWriteDLL = NULL;
72 
73 static PGETUSERDEFAULTLOCALENAME pGetUserDefaultLocaleName = NULL;
74 static PD2D1CREATEFACTORY pD2D1CreateFactory = NULL;
75 static PDWRITECREATEFACTORY pDWriteCreateFactory = NULL;
76 
77 #define GetUserDefaultLocaleName	(*pGetUserDefaultLocaleName)
78 #define D2D1CreateFactory		(*pD2D1CreateFactory)
79 #define DWriteCreateFactory		(*pDWriteCreateFactory)
80 
81     static void
82 unload(HINSTANCE &hinst)
83 {
84     if (hinst != NULL)
85     {
86 	FreeLibrary(hinst);
87 	hinst = NULL;
88     }
89 }
90 #endif // DYNAMIC_DIRECTX
91 
92 template <class T> inline void SafeRelease(T **ppT)
93 {
94     if (*ppT)
95     {
96 	(*ppT)->Release();
97 	*ppT = NULL;
98     }
99 }
100 
101     static DWRITE_PIXEL_GEOMETRY
102 ToPixelGeometry(int value)
103 {
104     switch (value)
105     {
106 	default:
107 	case 0:
108 	    return DWRITE_PIXEL_GEOMETRY_FLAT;
109 	case 1:
110 	    return DWRITE_PIXEL_GEOMETRY_RGB;
111 	case 2:
112 	    return DWRITE_PIXEL_GEOMETRY_BGR;
113     }
114 }
115 
116     static int
117 ToInt(DWRITE_PIXEL_GEOMETRY value)
118 {
119     switch (value)
120     {
121 	case DWRITE_PIXEL_GEOMETRY_FLAT:
122 	    return 0;
123 	case DWRITE_PIXEL_GEOMETRY_RGB:
124 	    return 1;
125 	case DWRITE_PIXEL_GEOMETRY_BGR:
126 	    return 2;
127 	default:
128 	    return -1;
129     }
130 }
131 
132     static DWRITE_RENDERING_MODE
133 ToRenderingMode(int value)
134 {
135     switch (value)
136     {
137 	default:
138 	case 0:
139 	    return DWRITE_RENDERING_MODE_DEFAULT;
140 	case 1:
141 	    return DWRITE_RENDERING_MODE_ALIASED;
142 	case 2:
143 	    return DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
144 	case 3:
145 	    return DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL;
146 	case 4:
147 	    return DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL;
148 	case 5:
149 	    return DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
150 	case 6:
151 	    return DWRITE_RENDERING_MODE_OUTLINE;
152     }
153 }
154 
155     static D2D1_TEXT_ANTIALIAS_MODE
156 ToTextAntialiasMode(int value)
157 {
158     switch (value)
159     {
160 	default:
161 	case 0:
162 	    return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
163 	case 1:
164 	    return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
165 	case 2:
166 	    return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
167 	case 3:
168 	    return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
169     }
170 }
171 
172     static int
173 ToInt(DWRITE_RENDERING_MODE value)
174 {
175     switch (value)
176     {
177 	case DWRITE_RENDERING_MODE_DEFAULT:
178 	    return 0;
179 	case DWRITE_RENDERING_MODE_ALIASED:
180 	    return 1;
181 	case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC:
182 	    return 2;
183 	case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL:
184 	    return 3;
185 	case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL:
186 	    return 4;
187 	case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC:
188 	    return 5;
189 	case DWRITE_RENDERING_MODE_OUTLINE:
190 	    return 6;
191 	default:
192 	    return -1;
193     }
194 }
195 
196 class FontCache {
197 public:
198     struct Item {
199 	HFONT              hFont;
200 	IDWriteTextFormat* pTextFormat;
201 	DWRITE_FONT_WEIGHT fontWeight;
202 	DWRITE_FONT_STYLE  fontStyle;
203 	Item() : hFont(NULL), pTextFormat(NULL) {}
204     };
205 
206 private:
207     int mSize;
208     Item *mItems;
209 
210 public:
211     FontCache(int size = 2) :
212 	mSize(size),
213 	mItems(new Item[size])
214     {
215     }
216 
217     ~FontCache()
218     {
219 	for (int i = 0; i < mSize; ++i)
220 	    SafeRelease(&mItems[i].pTextFormat);
221 	delete[] mItems;
222     }
223 
224     bool get(HFONT hFont, Item &item)
225     {
226 	int n = find(hFont);
227 	if (n < 0)
228 	    return false;
229 	item = mItems[n];
230 	slide(n);
231 	return true;
232     }
233 
234     void put(const Item& item)
235     {
236 	int n = find(item.hFont);
237 	if (n < 0)
238 	    n = mSize - 1;
239 	if (mItems[n].pTextFormat != item.pTextFormat)
240 	{
241 	    SafeRelease(&mItems[n].pTextFormat);
242 	    item.pTextFormat->AddRef();
243 	}
244 	mItems[n] = item;
245 	slide(n);
246     }
247 
248 private:
249     int find(HFONT hFont)
250     {
251 	for (int i = 0; i < mSize; ++i)
252 	{
253 	    if (mItems[i].hFont == hFont)
254 		return i;
255 	}
256 	return -1;
257     }
258 
259     void slide(int nextTop)
260     {
261 	if (nextTop == 0)
262 	    return;
263 	Item tmp = mItems[nextTop];
264 	for (int i = nextTop - 1; i >= 0; --i)
265 	    mItems[i + 1] = mItems[i];
266 	mItems[0] = tmp;
267     }
268 };
269 
270 enum DrawingMode {
271     DM_GDI = 0,
272     DM_DIRECTX = 1,
273     DM_INTEROP = 2,
274 };
275 
276 struct DWriteContext {
277     HDC mHDC;
278     RECT mBindRect;
279     DrawingMode mDMode;
280     HDC mInteropHDC;
281     bool mDrawing;
282     bool mFallbackDC;
283 
284     ID2D1Factory *mD2D1Factory;
285 
286     ID2D1DCRenderTarget *mRT;
287     ID2D1GdiInteropRenderTarget *mGDIRT;
288     ID2D1SolidColorBrush *mBrush;
289     ID2D1Bitmap *mBitmap;
290 
291     IDWriteFactory *mDWriteFactory;
292 #ifdef FEAT_DIRECTX_COLOR_EMOJI
293     IDWriteFactory2 *mDWriteFactory2;
294 #endif
295 
296     IDWriteGdiInterop *mGdiInterop;
297     IDWriteRenderingParams *mRenderingParams;
298 
299     FontCache mFontCache;
300     IDWriteTextFormat *mTextFormat;
301     DWRITE_FONT_WEIGHT mFontWeight;
302     DWRITE_FONT_STYLE mFontStyle;
303 
304     D2D1_TEXT_ANTIALIAS_MODE mTextAntialiasMode;
305 
306     // METHODS
307 
308     DWriteContext();
309 
310     virtual ~DWriteContext();
311 
312     HRESULT CreateDeviceResources();
313 
314     void DiscardDeviceResources();
315 
316     HRESULT CreateTextFormatFromLOGFONT(const LOGFONTW &logFont,
317 	    IDWriteTextFormat **ppTextFormat);
318 
319     HRESULT SetFontByLOGFONT(const LOGFONTW &logFont);
320 
321     void SetFont(HFONT hFont);
322 
323     void Rebind();
324 
325     void BindDC(HDC hdc, const RECT *rect);
326 
327     HRESULT SetDrawingMode(DrawingMode mode);
328 
329     ID2D1Brush* SolidBrush(COLORREF color);
330 
331     void DrawText(const WCHAR *text, int len,
332 	int x, int y, int w, int h, int cellWidth, COLORREF color,
333 	UINT fuOptions, const RECT *lprc, const INT *lpDx);
334 
335     void FillRect(const RECT *rc, COLORREF color);
336 
337     void DrawLine(int x1, int y1, int x2, int y2, COLORREF color);
338 
339     void SetPixel(int x, int y, COLORREF color);
340 
341     void Scroll(int x, int y, const RECT *rc);
342 
343     void Flush();
344 
345     void SetRenderingParams(
346 	    const DWriteRenderingParams *params);
347 
348     DWriteRenderingParams *GetRenderingParams(
349 	    DWriteRenderingParams *params);
350 };
351 
352 class AdjustedGlyphRun : public DWRITE_GLYPH_RUN
353 {
354 private:
355     FLOAT &mAccum;
356     FLOAT mDelta;
357     FLOAT *mAdjustedAdvances;
358 
359 public:
360     AdjustedGlyphRun(
361 	    const DWRITE_GLYPH_RUN *glyphRun,
362 	    FLOAT cellWidth,
363 	    FLOAT &accum) :
364 	DWRITE_GLYPH_RUN(*glyphRun),
365 	mAccum(accum),
366 	mDelta(0.0f),
367 	mAdjustedAdvances(new FLOAT[glyphRun->glyphCount])
368     {
369 	assert(cellWidth != 0.0f);
370 	for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
371 	{
372 	    FLOAT orig = glyphRun->glyphAdvances[i];
373 	    FLOAT adjusted = adjustToCell(orig, cellWidth);
374 	    mAdjustedAdvances[i] = adjusted;
375 	    mDelta += adjusted - orig;
376 	}
377 	glyphAdvances = mAdjustedAdvances;
378     }
379 
380     ~AdjustedGlyphRun()
381     {
382 	mAccum += mDelta;
383 	delete[] mAdjustedAdvances;
384     }
385 
386     static FLOAT adjustToCell(FLOAT value, FLOAT cellWidth)
387     {
388 	int cellCount = int(floor(value / cellWidth + 0.5f));
389 	if (cellCount < 1)
390 	    cellCount = 1;
391 	return cellCount * cellWidth;
392     }
393 };
394 
395 struct TextRendererContext {
396     // const fields.
397     COLORREF color;
398     FLOAT cellWidth;
399 
400     // working fields.
401     FLOAT offsetX;
402 };
403 
404 class TextRenderer FINAL : public IDWriteTextRenderer
405 {
406 public:
407     TextRenderer(
408 	    DWriteContext* pDWC) :
409 	cRefCount_(0),
410 	pDWC_(pDWC)
411     {
412 	AddRef();
413     }
414 
415     // add "virtual" to avoid a compiler warning
416     virtual ~TextRenderer()
417     {
418     }
419 
420     IFACEMETHOD(IsPixelSnappingDisabled)(
421 	__maybenull void* clientDrawingContext,
422 	__out BOOL* isDisabled)
423     {
424 	*isDisabled = FALSE;
425 	return S_OK;
426     }
427 
428     IFACEMETHOD(GetCurrentTransform)(
429 	__maybenull void* clientDrawingContext,
430 	__out DWRITE_MATRIX* transform)
431     {
432 	// forward the render target's transform
433 	pDWC_->mRT->GetTransform(
434 		reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform));
435 	return S_OK;
436     }
437 
438     IFACEMETHOD(GetPixelsPerDip)(
439 	__maybenull void* clientDrawingContext,
440 	__out FLOAT* pixelsPerDip)
441     {
442 	float dpiX, unused;
443 	pDWC_->mRT->GetDpi(&dpiX, &unused);
444 	*pixelsPerDip = dpiX / 96.0f;
445 	return S_OK;
446     }
447 
448     IFACEMETHOD(DrawUnderline)(
449 	__maybenull void* clientDrawingContext,
450 	FLOAT baselineOriginX,
451 	FLOAT baselineOriginY,
452 	__in DWRITE_UNDERLINE const* underline,
453 	IUnknown* clientDrawingEffect)
454     {
455 	return E_NOTIMPL;
456     }
457 
458     IFACEMETHOD(DrawStrikethrough)(
459 	__maybenull void* clientDrawingContext,
460 	FLOAT baselineOriginX,
461 	FLOAT baselineOriginY,
462 	__in DWRITE_STRIKETHROUGH const* strikethrough,
463 	IUnknown* clientDrawingEffect)
464     {
465 	return E_NOTIMPL;
466     }
467 
468     IFACEMETHOD(DrawInlineObject)(
469 	__maybenull void* clientDrawingContext,
470 	FLOAT originX,
471 	FLOAT originY,
472 	IDWriteInlineObject* inlineObject,
473 	BOOL isSideways,
474 	BOOL isRightToLeft,
475 	IUnknown* clientDrawingEffect)
476     {
477 	return E_NOTIMPL;
478     }
479 
480     IFACEMETHOD(DrawGlyphRun)(
481 	__maybenull void* clientDrawingContext,
482 	FLOAT baselineOriginX,
483 	FLOAT baselineOriginY,
484 	DWRITE_MEASURING_MODE measuringMode,
485 	__in DWRITE_GLYPH_RUN const* glyphRun,
486 	__in DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
487 	IUnknown* clientDrawingEffect)
488     {
489 	TextRendererContext *context =
490 	    reinterpret_cast<TextRendererContext*>(clientDrawingContext);
491 
492 	AdjustedGlyphRun adjustedGlyphRun(glyphRun, context->cellWidth,
493 		context->offsetX);
494 
495 #ifdef FEAT_DIRECTX_COLOR_EMOJI
496 	if (pDWC_->mDWriteFactory2 != NULL)
497 	{
498 	    IDWriteColorGlyphRunEnumerator *enumerator = NULL;
499 	    HRESULT hr = pDWC_->mDWriteFactory2->TranslateColorGlyphRun(
500 		baselineOriginX + context->offsetX,
501 		baselineOriginY,
502 		&adjustedGlyphRun,
503 		NULL,
504 		DWRITE_MEASURING_MODE_GDI_NATURAL,
505 		NULL,
506 		0,
507 		&enumerator);
508 	    if (SUCCEEDED(hr))
509 	    {
510 		// Draw by IDWriteFactory2 for color emoji
511 		BOOL hasRun = TRUE;
512 		enumerator->MoveNext(&hasRun);
513 		while (hasRun)
514 		{
515 		    const DWRITE_COLOR_GLYPH_RUN* colorGlyphRun;
516 		    enumerator->GetCurrentRun(&colorGlyphRun);
517 
518 		    pDWC_->mBrush->SetColor(colorGlyphRun->runColor);
519 		    pDWC_->mRT->DrawGlyphRun(
520 			    D2D1::Point2F(
521 				colorGlyphRun->baselineOriginX,
522 				colorGlyphRun->baselineOriginY),
523 			    &colorGlyphRun->glyphRun,
524 			    pDWC_->mBrush,
525 			    DWRITE_MEASURING_MODE_NATURAL);
526 		    enumerator->MoveNext(&hasRun);
527 		}
528 		SafeRelease(&enumerator);
529 		return S_OK;
530 	    }
531 	}
532 #endif
533 
534 	// Draw by IDWriteFactory (without color emoji)
535 	pDWC_->mRT->DrawGlyphRun(
536 		D2D1::Point2F(
537 		    baselineOriginX + context->offsetX,
538 		    baselineOriginY),
539 		&adjustedGlyphRun,
540 		pDWC_->SolidBrush(context->color),
541 		DWRITE_MEASURING_MODE_NATURAL);
542 	return S_OK;
543     }
544 
545 public:
546     IFACEMETHOD_(unsigned long, AddRef) ()
547     {
548 	return InterlockedIncrement(&cRefCount_);
549     }
550 
551     IFACEMETHOD_(unsigned long, Release) ()
552     {
553 	long newCount = InterlockedDecrement(&cRefCount_);
554 
555 	if (newCount == 0)
556 	{
557 	    delete this;
558 	    return 0;
559 	}
560 	return newCount;
561     }
562 
563     IFACEMETHOD(QueryInterface)(
564 	IID const& riid,
565 	void** ppvObject)
566     {
567 	if (__uuidof(IDWriteTextRenderer) == riid)
568 	{
569 	    *ppvObject = this;
570 	}
571 	else if (__uuidof(IDWritePixelSnapping) == riid)
572 	{
573 	    *ppvObject = this;
574 	}
575 	else if (__uuidof(IUnknown) == riid)
576 	{
577 	    *ppvObject = this;
578 	}
579 	else
580 	{
581 	    *ppvObject = NULL;
582 	    return E_FAIL;
583 	}
584 
585 	return S_OK;
586     }
587 
588 private:
589     long cRefCount_;
590     DWriteContext* pDWC_;
591 };
592 
593 DWriteContext::DWriteContext() :
594     mHDC(NULL),
595     mBindRect(),
596     mDMode(DM_GDI),
597     mInteropHDC(NULL),
598     mDrawing(false),
599     mFallbackDC(false),
600     mD2D1Factory(NULL),
601     mRT(NULL),
602     mGDIRT(NULL),
603     mBrush(NULL),
604     mBitmap(NULL),
605     mDWriteFactory(NULL),
606 #ifdef FEAT_DIRECTX_COLOR_EMOJI
607     mDWriteFactory2(NULL),
608 #endif
609     mGdiInterop(NULL),
610     mRenderingParams(NULL),
611     mFontCache(8),
612     mTextFormat(NULL),
613     mFontWeight(DWRITE_FONT_WEIGHT_NORMAL),
614     mFontStyle(DWRITE_FONT_STYLE_NORMAL),
615     mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT)
616 {
617     HRESULT hr;
618 
619     hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
620 	    __uuidof(ID2D1Factory), NULL,
621 	    reinterpret_cast<void**>(&mD2D1Factory));
622     _RPT2(_CRT_WARN, "D2D1CreateFactory: hr=%p p=%p\n", hr, mD2D1Factory);
623 
624     if (SUCCEEDED(hr))
625     {
626 	hr = DWriteCreateFactory(
627 		DWRITE_FACTORY_TYPE_SHARED,
628 		__uuidof(IDWriteFactory),
629 		reinterpret_cast<IUnknown**>(&mDWriteFactory));
630 	_RPT2(_CRT_WARN, "DWriteCreateFactory: hr=%p p=%p\n", hr,
631 		mDWriteFactory);
632     }
633 
634 #ifdef FEAT_DIRECTX_COLOR_EMOJI
635     if (SUCCEEDED(hr))
636     {
637 	DWriteCreateFactory(
638 		DWRITE_FACTORY_TYPE_SHARED,
639 		__uuidof(IDWriteFactory2),
640 		reinterpret_cast<IUnknown**>(&mDWriteFactory2));
641 	_RPT1(_CRT_WARN, "IDWriteFactory2: %s\n", SUCCEEDED(hr) ? "available" : "not available");
642     }
643 #endif
644 
645     if (SUCCEEDED(hr))
646     {
647 	hr = mDWriteFactory->GetGdiInterop(&mGdiInterop);
648 	_RPT2(_CRT_WARN, "GetGdiInterop: hr=%p p=%p\n", hr, mGdiInterop);
649     }
650 
651     if (SUCCEEDED(hr))
652     {
653 	hr = mDWriteFactory->CreateRenderingParams(&mRenderingParams);
654 	_RPT2(_CRT_WARN, "CreateRenderingParams: hr=%p p=%p\n", hr,
655 		mRenderingParams);
656     }
657 }
658 
659 DWriteContext::~DWriteContext()
660 {
661     SafeRelease(&mTextFormat);
662     SafeRelease(&mRenderingParams);
663     SafeRelease(&mGdiInterop);
664     SafeRelease(&mDWriteFactory);
665 #ifdef FEAT_DIRECTX_COLOR_EMOJI
666     SafeRelease(&mDWriteFactory2);
667 #endif
668     SafeRelease(&mBitmap);
669     SafeRelease(&mBrush);
670     SafeRelease(&mGDIRT);
671     SafeRelease(&mRT);
672     SafeRelease(&mD2D1Factory);
673 }
674 
675     HRESULT
676 DWriteContext::CreateDeviceResources()
677 {
678     HRESULT hr;
679 
680     if (mRT != NULL)
681 	return S_OK;
682 
683     D2D1_RENDER_TARGET_PROPERTIES props = {
684 	D2D1_RENDER_TARGET_TYPE_DEFAULT,
685 	{ DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE },
686 	0, 0,
687 	D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE,
688 	D2D1_FEATURE_LEVEL_DEFAULT
689     };
690     hr = mD2D1Factory->CreateDCRenderTarget(&props, &mRT);
691     _RPT2(_CRT_WARN, "CreateDCRenderTarget: hr=%p p=%p\n", hr, mRT);
692 
693     if (SUCCEEDED(hr))
694     {
695 	// This always succeeds.
696 	mRT->QueryInterface(
697 		__uuidof(ID2D1GdiInteropRenderTarget),
698 		reinterpret_cast<void**>(&mGDIRT));
699 	_RPT1(_CRT_WARN, "GdiInteropRenderTarget: p=%p\n", mGDIRT);
700     }
701 
702     if (SUCCEEDED(hr))
703     {
704 	hr = mRT->CreateSolidColorBrush(
705 		D2D1::ColorF(D2D1::ColorF::Black),
706 		&mBrush);
707 	_RPT2(_CRT_WARN, "CreateSolidColorBrush: hr=%p p=%p\n", hr, mBrush);
708     }
709 
710     if (SUCCEEDED(hr))
711 	Rebind();
712 
713     return hr;
714 }
715 
716     void
717 DWriteContext::DiscardDeviceResources()
718 {
719     SafeRelease(&mBitmap);
720     SafeRelease(&mBrush);
721     SafeRelease(&mGDIRT);
722     SafeRelease(&mRT);
723 }
724 
725     HRESULT
726 DWriteContext::CreateTextFormatFromLOGFONT(const LOGFONTW &logFont,
727 	IDWriteTextFormat **ppTextFormat)
728 {
729     // Most of this function is copied from: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/RenderTest/TextHelpers.cpp
730     HRESULT hr = S_OK;
731     IDWriteTextFormat *pTextFormat = NULL;
732 
733     IDWriteFont *font = NULL;
734     IDWriteFontFamily *fontFamily = NULL;
735     IDWriteLocalizedStrings *localizedFamilyNames = NULL;
736     float fontSize = 0;
737 
738     if (SUCCEEDED(hr))
739     {
740 	hr = mGdiInterop->CreateFontFromLOGFONT(&logFont, &font);
741     }
742 
743     // Get the font family to which this font belongs.
744     if (SUCCEEDED(hr))
745     {
746 	hr = font->GetFontFamily(&fontFamily);
747     }
748 
749     // Get the family names. This returns an object that encapsulates one or
750     // more names with the same meaning but in different languages.
751     if (SUCCEEDED(hr))
752     {
753 	hr = fontFamily->GetFamilyNames(&localizedFamilyNames);
754     }
755 
756     // Get the family name at index zero. If we were going to display the name
757     // we'd want to try to find one that matched the use locale, but for
758     // purposes of creating a text format object any language will do.
759 
760     wchar_t familyName[100];
761     if (SUCCEEDED(hr))
762     {
763 	hr = localizedFamilyNames->GetString(0, familyName,
764 		ARRAYSIZE(familyName));
765     }
766 
767     if (SUCCEEDED(hr))
768     {
769 	// Use lfHeight of the LOGFONT as font size.
770 	fontSize = float(logFont.lfHeight);
771 
772 	if (fontSize < 0)
773 	{
774 	    // Negative lfHeight represents the size of the em unit.
775 	    fontSize = -fontSize;
776 	}
777 	else
778 	{
779 	    // Positive lfHeight represents the cell height (ascent +
780 	    // descent).
781 	    DWRITE_FONT_METRICS fontMetrics;
782 	    font->GetMetrics(&fontMetrics);
783 
784 	    // Convert the cell height (ascent + descent) from design units
785 	    // to ems.
786 	    float cellHeight = static_cast<float>(
787 		    fontMetrics.ascent + fontMetrics.descent)
788 		/ fontMetrics.designUnitsPerEm;
789 
790 	    // Divide the font size by the cell height to get the font em
791 	    // size.
792 	    fontSize /= cellHeight;
793 	}
794     }
795 
796     // The text format includes a locale name. Ideally, this would be the
797     // language of the text, which may or may not be the same as the primary
798     // language of the user. However, for our purposes the user locale will do.
799     wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
800     if (SUCCEEDED(hr))
801     {
802 	if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0)
803 	    hr = HRESULT_FROM_WIN32(GetLastError());
804     }
805 
806     if (SUCCEEDED(hr))
807     {
808 	// Create the text format object.
809 	hr = mDWriteFactory->CreateTextFormat(
810 		familyName,
811 		NULL, // no custom font collection
812 		font->GetWeight(),
813 		font->GetStyle(),
814 		font->GetStretch(),
815 		fontSize,
816 		localeName,
817 		&pTextFormat);
818     }
819 
820     if (SUCCEEDED(hr))
821 	hr = pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
822 
823     if (SUCCEEDED(hr))
824 	hr = pTextFormat->SetParagraphAlignment(
825 		DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
826 
827     if (SUCCEEDED(hr))
828 	hr = pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
829 
830     SafeRelease(&localizedFamilyNames);
831     SafeRelease(&fontFamily);
832     SafeRelease(&font);
833 
834     if (SUCCEEDED(hr))
835 	*ppTextFormat = pTextFormat;
836     else
837 	SafeRelease(&pTextFormat);
838 
839     return hr;
840 }
841 
842     HRESULT
843 DWriteContext::SetFontByLOGFONT(const LOGFONTW &logFont)
844 {
845     HRESULT hr = S_OK;
846     IDWriteTextFormat *pTextFormat = NULL;
847 
848     hr = CreateTextFormatFromLOGFONT(logFont, &pTextFormat);
849 
850     if (SUCCEEDED(hr))
851     {
852 	SafeRelease(&mTextFormat);
853 	mTextFormat = pTextFormat;
854 	mFontWeight = static_cast<DWRITE_FONT_WEIGHT>(logFont.lfWeight);
855 	mFontStyle = logFont.lfItalic ? DWRITE_FONT_STYLE_ITALIC
856 	    : DWRITE_FONT_STYLE_NORMAL;
857     }
858 
859     return hr;
860 }
861 
862     void
863 DWriteContext::SetFont(HFONT hFont)
864 {
865     FontCache::Item item;
866     if (mFontCache.get(hFont, item))
867     {
868 	if (item.pTextFormat != NULL)
869 	{
870 	    item.pTextFormat->AddRef();
871 	    SafeRelease(&mTextFormat);
872 	    mTextFormat = item.pTextFormat;
873 	    mFontWeight = item.fontWeight;
874 	    mFontStyle = item.fontStyle;
875 	    mFallbackDC = false;
876 	}
877 	else
878 	    mFallbackDC = true;
879 	return;
880     }
881 
882     HRESULT hr = E_FAIL;
883     LOGFONTW lf;
884     if (GetObjectW(hFont, sizeof(lf), &lf))
885 	hr = SetFontByLOGFONT(lf);
886 
887     item.hFont = hFont;
888     if (SUCCEEDED(hr))
889     {
890 	item.pTextFormat = mTextFormat;
891 	item.fontWeight = mFontWeight;
892 	item.fontStyle = mFontStyle;
893 	mFallbackDC = false;
894     }
895     else
896 	mFallbackDC = true;
897     mFontCache.put(item);
898 }
899 
900     void
901 DWriteContext::Rebind()
902 {
903     SafeRelease(&mBitmap);
904 
905     mRT->BindDC(mHDC, &mBindRect);
906     mRT->SetTransform(D2D1::IdentityMatrix());
907 
908     D2D1_BITMAP_PROPERTIES props = {
909 	{DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE},
910 	96.0f, 96.0f
911     };
912     mRT->CreateBitmap(
913 	    D2D1::SizeU(mBindRect.right - mBindRect.left,
914 		mBindRect.bottom - mBindRect.top),
915 	    props, &mBitmap);
916 }
917 
918     void
919 DWriteContext::BindDC(HDC hdc, const RECT *rect)
920 {
921     mHDC = hdc;
922     mBindRect = *rect;
923 
924     if (mRT == NULL)
925 	CreateDeviceResources();
926     else
927     {
928 	Flush();
929 	Rebind();
930     }
931 }
932 
933     HRESULT
934 DWriteContext::SetDrawingMode(DrawingMode mode)
935 {
936     HRESULT hr = S_OK;
937 
938     switch (mode)
939     {
940 	default:
941 	case DM_GDI:
942 	    if (mInteropHDC != NULL)
943 	    {
944 		mGDIRT->ReleaseDC(NULL);
945 		mInteropHDC = NULL;
946 	    }
947 	    if (mDrawing)
948 	    {
949 		hr = mRT->EndDraw();
950 		if (hr == D2DERR_RECREATE_TARGET)
951 		{
952 		    hr = S_OK;
953 		    DiscardDeviceResources();
954 		    CreateDeviceResources();
955 		}
956 		mDrawing = false;
957 	    }
958 	    break;
959 
960 	case DM_DIRECTX:
961 	    if (mInteropHDC != NULL)
962 	    {
963 		mGDIRT->ReleaseDC(NULL);
964 		mInteropHDC = NULL;
965 	    }
966 	    else if (mDrawing == false)
967 	    {
968 		CreateDeviceResources();
969 		mRT->BeginDraw();
970 		mDrawing = true;
971 	    }
972 	    break;
973 
974 	case DM_INTEROP:
975 	    if (mDrawing == false)
976 	    {
977 		CreateDeviceResources();
978 		mRT->BeginDraw();
979 		mDrawing = true;
980 	    }
981 	    if (mInteropHDC == NULL)
982 		hr = mGDIRT->GetDC(D2D1_DC_INITIALIZE_MODE_COPY, &mInteropHDC);
983 	    break;
984     }
985     mDMode = mode;
986     return hr;
987 }
988 
989     ID2D1Brush*
990 DWriteContext::SolidBrush(COLORREF color)
991 {
992     mBrush->SetColor(D2D1::ColorF(UINT32(GetRValue(color)) << 16 |
993 		UINT32(GetGValue(color)) << 8 | UINT32(GetBValue(color))));
994     return mBrush;
995 }
996 
997     void
998 DWriteContext::DrawText(const WCHAR *text, int len,
999 	int x, int y, int w, int h, int cellWidth, COLORREF color,
1000 	UINT fuOptions, const RECT *lprc, const INT *lpDx)
1001 {
1002     if (mFallbackDC)
1003     {
1004 	// Fall back to GDI rendering.
1005 	HRESULT hr = SetDrawingMode(DM_INTEROP);
1006 	if (SUCCEEDED(hr))
1007 	{
1008 	    HGDIOBJ hFont = ::GetCurrentObject(mHDC, OBJ_FONT);
1009 	    HGDIOBJ hOldFont = ::SelectObject(mInteropHDC, hFont);
1010 	    ::SetTextColor(mInteropHDC, color);
1011 	    ::SetBkMode(mInteropHDC, ::GetBkMode(mHDC));
1012 	    ::ExtTextOutW(mInteropHDC, x, y, fuOptions, lprc, text, len, lpDx);
1013 	    ::SelectObject(mInteropHDC, hOldFont);
1014 	}
1015 	return;
1016     }
1017 
1018     HRESULT hr;
1019     IDWriteTextLayout *textLayout = NULL;
1020 
1021     SetDrawingMode(DM_DIRECTX);
1022 
1023     hr = mDWriteFactory->CreateTextLayout(text, len, mTextFormat,
1024 	    FLOAT(w), FLOAT(h), &textLayout);
1025 
1026     if (SUCCEEDED(hr))
1027     {
1028 	DWRITE_TEXT_RANGE textRange = { 0, UINT32(len) };
1029 	textLayout->SetFontWeight(mFontWeight, textRange);
1030 	textLayout->SetFontStyle(mFontStyle, textRange);
1031 
1032 	TextRenderer renderer(this);
1033 	TextRendererContext context = { color, FLOAT(cellWidth), 0.0f };
1034 	textLayout->Draw(&context, &renderer, FLOAT(x), FLOAT(y) - 0.5f);
1035     }
1036 
1037     SafeRelease(&textLayout);
1038 }
1039 
1040     void
1041 DWriteContext::FillRect(const RECT *rc, COLORREF color)
1042 {
1043     if (mDMode == DM_INTEROP)
1044     {
1045 	// GDI functions are used before this call.  Keep using GDI.
1046 	// (Switching to Direct2D causes terrible slowdown.)
1047 	HBRUSH hbr = ::CreateSolidBrush(color);
1048 	::FillRect(mInteropHDC, rc, hbr);
1049 	::DeleteObject(HGDIOBJ(hbr));
1050     }
1051     else
1052     {
1053 	SetDrawingMode(DM_DIRECTX);
1054 	mRT->FillRectangle(
1055 		D2D1::RectF(FLOAT(rc->left), FLOAT(rc->top),
1056 		    FLOAT(rc->right), FLOAT(rc->bottom)),
1057 		SolidBrush(color));
1058     }
1059 }
1060 
1061     void
1062 DWriteContext::DrawLine(int x1, int y1, int x2, int y2, COLORREF color)
1063 {
1064     if (mDMode == DM_INTEROP)
1065     {
1066 	// GDI functions are used before this call.  Keep using GDI.
1067 	// (Switching to Direct2D causes terrible slowdown.)
1068 	HPEN hpen = ::CreatePen(PS_SOLID, 1, color);
1069 	HGDIOBJ old_pen = ::SelectObject(mInteropHDC, HGDIOBJ(hpen));
1070 	::MoveToEx(mInteropHDC, x1, y1, NULL);
1071 	::LineTo(mInteropHDC, x2, y2);
1072 	::SelectObject(mInteropHDC, old_pen);
1073 	::DeleteObject(HGDIOBJ(hpen));
1074     }
1075     else
1076     {
1077 	SetDrawingMode(DM_DIRECTX);
1078 	mRT->DrawLine(
1079 		D2D1::Point2F(FLOAT(x1), FLOAT(y1) + 0.5f),
1080 		D2D1::Point2F(FLOAT(x2), FLOAT(y2) + 0.5f),
1081 		SolidBrush(color));
1082     }
1083 }
1084 
1085     void
1086 DWriteContext::SetPixel(int x, int y, COLORREF color)
1087 {
1088     if (mDMode == DM_INTEROP)
1089     {
1090 	// GDI functions are used before this call.  Keep using GDI.
1091 	// (Switching to Direct2D causes terrible slowdown.)
1092 	::SetPixel(mInteropHDC, x, y, color);
1093     }
1094     else
1095     {
1096 	SetDrawingMode(DM_DIRECTX);
1097 	// Direct2D doesn't have SetPixel API.  Use DrawLine instead.
1098 	mRT->DrawLine(
1099 		D2D1::Point2F(FLOAT(x), FLOAT(y) + 0.5f),
1100 		D2D1::Point2F(FLOAT(x+1), FLOAT(y) + 0.5f),
1101 		SolidBrush(color));
1102     }
1103 }
1104 
1105     void
1106 DWriteContext::Scroll(int x, int y, const RECT *rc)
1107 {
1108     SetDrawingMode(DM_DIRECTX);
1109     mRT->Flush();
1110 
1111     D2D1_RECT_U srcRect;
1112     D2D1_POINT_2U destPoint;
1113     if (x >= 0)
1114     {
1115 	srcRect.left = rc->left;
1116 	srcRect.right = rc->right - x;
1117 	destPoint.x = rc->left + x;
1118     }
1119     else
1120     {
1121 	srcRect.left = rc->left - x;
1122 	srcRect.right = rc->right;
1123 	destPoint.x = rc->left;
1124     }
1125     if (y >= 0)
1126     {
1127 	srcRect.top = rc->top;
1128 	srcRect.bottom = rc->bottom - y;
1129 	destPoint.y = rc->top + y;
1130     }
1131     else
1132     {
1133 	srcRect.top = rc->top - y;
1134 	srcRect.bottom = rc->bottom;
1135 	destPoint.y = rc->top;
1136     }
1137     mBitmap->CopyFromRenderTarget(&destPoint, mRT, &srcRect);
1138 
1139     D2D1_RECT_F destRect = {
1140 	    FLOAT(destPoint.x), FLOAT(destPoint.y),
1141 	    FLOAT(destPoint.x + srcRect.right - srcRect.left),
1142 	    FLOAT(destPoint.y + srcRect.bottom - srcRect.top)
1143     };
1144     mRT->DrawBitmap(mBitmap, destRect, 1.0F,
1145 	    D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, destRect);
1146 }
1147 
1148     void
1149 DWriteContext::Flush()
1150 {
1151     SetDrawingMode(DM_GDI);
1152 }
1153 
1154     void
1155 DWriteContext::SetRenderingParams(
1156 	const DWriteRenderingParams *params)
1157 {
1158     if (mDWriteFactory == NULL)
1159 	return;
1160 
1161     IDWriteRenderingParams *renderingParams = NULL;
1162     D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode =
1163 	D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
1164     HRESULT hr;
1165     if (params != NULL)
1166     {
1167 	hr = mDWriteFactory->CreateCustomRenderingParams(params->gamma,
1168 		params->enhancedContrast, params->clearTypeLevel,
1169 		ToPixelGeometry(params->pixelGeometry),
1170 		ToRenderingMode(params->renderingMode), &renderingParams);
1171 	textAntialiasMode = ToTextAntialiasMode(params->textAntialiasMode);
1172     }
1173     else
1174 	hr = mDWriteFactory->CreateRenderingParams(&renderingParams);
1175     if (SUCCEEDED(hr) && renderingParams != NULL)
1176     {
1177 	SafeRelease(&mRenderingParams);
1178 	mRenderingParams = renderingParams;
1179 	mTextAntialiasMode = textAntialiasMode;
1180 
1181 	Flush();
1182 	mRT->SetTextRenderingParams(mRenderingParams);
1183 	mRT->SetTextAntialiasMode(mTextAntialiasMode);
1184     }
1185 }
1186 
1187     DWriteRenderingParams *
1188 DWriteContext::GetRenderingParams(
1189 	DWriteRenderingParams *params)
1190 {
1191     if (params != NULL && mRenderingParams != NULL)
1192     {
1193 	params->gamma = mRenderingParams->GetGamma();
1194 	params->enhancedContrast = mRenderingParams->GetEnhancedContrast();
1195 	params->clearTypeLevel = mRenderingParams->GetClearTypeLevel();
1196 	params->pixelGeometry = ToInt(mRenderingParams->GetPixelGeometry());
1197 	params->renderingMode = ToInt(mRenderingParams->GetRenderingMode());
1198 	params->textAntialiasMode = mTextAntialiasMode;
1199     }
1200     return params;
1201 }
1202 
1203 ////////////////////////////////////////////////////////////////////////////
1204 // PUBLIC C INTERFACES
1205 
1206     void
1207 DWrite_Init(void)
1208 {
1209 #ifdef DYNAMIC_DIRECTX
1210     // Load libraries.
1211     hD2D1DLL = vimLoadLib(const_cast<char*>("d2d1.dll"));
1212     hDWriteDLL = vimLoadLib(const_cast<char*>("dwrite.dll"));
1213     if (hD2D1DLL == NULL || hDWriteDLL == NULL)
1214     {
1215 	DWrite_Final();
1216 	return;
1217     }
1218     // Get address of procedures.
1219     pGetUserDefaultLocaleName = (PGETUSERDEFAULTLOCALENAME)GetProcAddress(
1220 	    GetModuleHandle("kernel32.dll"), "GetUserDefaultLocaleName");
1221     pD2D1CreateFactory = (PD2D1CREATEFACTORY)GetProcAddress(hD2D1DLL,
1222 	    "D2D1CreateFactory");
1223     pDWriteCreateFactory = (PDWRITECREATEFACTORY)GetProcAddress(hDWriteDLL,
1224 	    "DWriteCreateFactory");
1225 #endif
1226 }
1227 
1228     void
1229 DWrite_Final(void)
1230 {
1231 #ifdef DYNAMIC_DIRECTX
1232     pGetUserDefaultLocaleName = NULL;
1233     pD2D1CreateFactory = NULL;
1234     pDWriteCreateFactory = NULL;
1235     unload(hDWriteDLL);
1236     unload(hD2D1DLL);
1237 #endif
1238 }
1239 
1240     DWriteContext *
1241 DWriteContext_Open(void)
1242 {
1243 #ifdef DYNAMIC_DIRECTX
1244     if (pGetUserDefaultLocaleName == NULL || pD2D1CreateFactory == NULL
1245 	    || pDWriteCreateFactory == NULL)
1246 	return NULL;
1247 #endif
1248     return new DWriteContext();
1249 }
1250 
1251     void
1252 DWriteContext_BindDC(DWriteContext *ctx, HDC hdc, const RECT *rect)
1253 {
1254     if (ctx != NULL)
1255 	ctx->BindDC(hdc, rect);
1256 }
1257 
1258     void
1259 DWriteContext_SetFont(DWriteContext *ctx, HFONT hFont)
1260 {
1261     if (ctx != NULL)
1262 	ctx->SetFont(hFont);
1263 }
1264 
1265     void
1266 DWriteContext_DrawText(
1267 	DWriteContext *ctx,
1268 	const WCHAR *text,
1269 	int len,
1270 	int x,
1271 	int y,
1272 	int w,
1273 	int h,
1274 	int cellWidth,
1275 	COLORREF color,
1276 	UINT fuOptions,
1277 	const RECT *lprc,
1278 	const INT *lpDx)
1279 {
1280     if (ctx != NULL)
1281 	ctx->DrawText(text, len, x, y, w, h, cellWidth, color,
1282 		fuOptions, lprc, lpDx);
1283 }
1284 
1285     void
1286 DWriteContext_FillRect(DWriteContext *ctx, const RECT *rc, COLORREF color)
1287 {
1288     if (ctx != NULL)
1289 	ctx->FillRect(rc, color);
1290 }
1291 
1292     void
1293 DWriteContext_DrawLine(DWriteContext *ctx, int x1, int y1, int x2, int y2,
1294 	COLORREF color)
1295 {
1296     if (ctx != NULL)
1297 	ctx->DrawLine(x1, y1, x2, y2, color);
1298 }
1299 
1300     void
1301 DWriteContext_SetPixel(DWriteContext *ctx, int x, int y, COLORREF color)
1302 {
1303     if (ctx != NULL)
1304 	ctx->SetPixel(x, y, color);
1305 }
1306 
1307     void
1308 DWriteContext_Scroll(DWriteContext *ctx, int x, int y, const RECT *rc)
1309 {
1310     if (ctx != NULL)
1311 	ctx->Scroll(x, y, rc);
1312 }
1313 
1314     void
1315 DWriteContext_Flush(DWriteContext *ctx)
1316 {
1317     if (ctx != NULL)
1318 	ctx->Flush();
1319 }
1320 
1321     void
1322 DWriteContext_Close(DWriteContext *ctx)
1323 {
1324     delete ctx;
1325 }
1326 
1327     void
1328 DWriteContext_SetRenderingParams(
1329 	DWriteContext *ctx,
1330 	const DWriteRenderingParams *params)
1331 {
1332     if (ctx != NULL)
1333 	ctx->SetRenderingParams(params);
1334 }
1335 
1336     DWriteRenderingParams *
1337 DWriteContext_GetRenderingParams(
1338 	DWriteContext *ctx,
1339 	DWriteRenderingParams *params)
1340 {
1341     if (ctx != NULL)
1342 	return ctx->GetRenderingParams(params);
1343     else
1344 	return NULL;
1345 }
1346