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