1 // Scintilla source code edit control
2 /** @file PlatWin.cxx
3  ** Implementation of platform facilities on Windows.
4  **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstring>
11 #include <cstdio>
12 #include <cstdarg>
13 #include <ctime>
14 #include <cmath>
15 #include <climits>
16 
17 #include <string_view>
18 #include <vector>
19 #include <map>
20 #include <algorithm>
21 #include <iterator>
22 #include <memory>
23 #include <mutex>
24 
25 // Want to use std::min and std::max so don't want Windows.h version of min and max
26 #if !defined(NOMINMAX)
27 #define NOMINMAX
28 #endif
29 #undef _WIN32_WINNT
30 #define _WIN32_WINNT 0x0500
31 #undef WINVER
32 #define WINVER 0x0500
33 #include <windows.h>
34 #include <commctrl.h>
35 #include <richedit.h>
36 #include <windowsx.h>
37 
38 #if !defined(DISABLE_D2D)
39 #define USE_D2D 1
40 #endif
41 
42 #if defined(USE_D2D)
43 #include <d2d1.h>
44 #include <dwrite.h>
45 #endif
46 
47 #include "Platform.h"
48 #include "XPM.h"
49 #include "UniConversion.h"
50 #include "DBCS.h"
51 #include "FontQuality.h"
52 
53 #include "PlatWin.h"
54 
55 #ifndef SPI_GETFONTSMOOTHINGCONTRAST
56 #define SPI_GETFONTSMOOTHINGCONTRAST	0x200C
57 #endif
58 
59 #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32
60 #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
61 #endif
62 
63 // __uuidof is a Microsoft extension but makes COM code neater, so disable warning
64 #if defined(__clang__)
65 #pragma clang diagnostic ignored "-Wlanguage-extension-token"
66 #endif
67 
68 namespace Scintilla {
69 
70 UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) noexcept;
71 
72 #if defined(USE_D2D)
73 IDWriteFactory *pIDWriteFactory = nullptr;
74 ID2D1Factory *pD2DFactory = nullptr;
75 IDWriteRenderingParams *defaultRenderingParams = nullptr;
76 IDWriteRenderingParams *customClearTypeRenderingParams = nullptr;
77 D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE;
78 
79 static HMODULE hDLLD2D {};
80 static HMODULE hDLLDWrite {};
81 
LoadD2DOnce()82 void LoadD2DOnce() noexcept {
83 	DWORD loadLibraryFlags = 0;
84 	HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");
85 	if (kernel32) {
86 		if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) {
87 			// Availability of SetDefaultDllDirectories implies Windows 8+ or
88 			// that KB2533623 has been installed so LoadLibraryEx can be called
89 			// with LOAD_LIBRARY_SEARCH_SYSTEM32.
90 			loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32;
91 		}
92 	}
93 
94 	typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
95 		CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
96 	typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
97 		IUnknown **factory);
98 
99 	hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, loadLibraryFlags);
100 	D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory");
101 	if (fnD2DCF) {
102 		// A single threaded factory as Scintilla always draw on the GUI thread
103 		fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
104 			__uuidof(ID2D1Factory),
105 			nullptr,
106 			reinterpret_cast<IUnknown**>(&pD2DFactory));
107 	}
108 	hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, loadLibraryFlags);
109 	DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory");
110 	if (fnDWCF) {
111 		const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec
112 		{ 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } };
113 
114 		const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
115 			IID_IDWriteFactory2,
116 			reinterpret_cast<IUnknown**>(&pIDWriteFactory));
117 		if (SUCCEEDED(hr)) {
118 			// D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT
119 			d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004);
120 		} else {
121 			fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
122 				__uuidof(IDWriteFactory),
123 				reinterpret_cast<IUnknown**>(&pIDWriteFactory));
124 		}
125 	}
126 
127 	if (pIDWriteFactory) {
128 		const HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams);
129 		if (SUCCEEDED(hr)) {
130 			unsigned int clearTypeContrast;
131 			if (::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0)) {
132 
133 				FLOAT gamma;
134 				if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200)
135 					gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f;
136 				else
137 					gamma = defaultRenderingParams->GetGamma();
138 
139 				pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(),
140 					defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams);
141 			}
142 		}
143 	}
144 }
145 
LoadD2D()146 bool LoadD2D() {
147 	static std::once_flag once;
148 	std::call_once(once, LoadD2DOnce);
149 	return pIDWriteFactory && pD2DFactory;
150 }
151 
152 #endif
153 
154 struct FormatAndMetrics {
155 	int technology;
156 	HFONT hfont;
157 #if defined(USE_D2D)
158 	IDWriteTextFormat *pTextFormat;
159 #endif
160 	int extraFontFlag;
161 	int characterSet;
162 	FLOAT yAscent;
163 	FLOAT yDescent;
164 	FLOAT yInternalLeading;
FormatAndMetricsScintilla::FormatAndMetrics165 	FormatAndMetrics(HFONT hfont_, int extraFontFlag_, int characterSet_) noexcept :
166 		technology(SCWIN_TECH_GDI), hfont(hfont_),
167 #if defined(USE_D2D)
168 		pTextFormat(nullptr),
169 #endif
170 		extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) {
171 	}
172 #if defined(USE_D2D)
FormatAndMetricsScintilla::FormatAndMetrics173 	FormatAndMetrics(IDWriteTextFormat *pTextFormat_,
174 	        int extraFontFlag_,
175 	        int characterSet_,
176 	        FLOAT yAscent_,
177 	        FLOAT yDescent_,
178 	        FLOAT yInternalLeading_) noexcept :
179 		technology(SCWIN_TECH_DIRECTWRITE),
180 		hfont{},
181 		pTextFormat(pTextFormat_),
182 		extraFontFlag(extraFontFlag_),
183 		characterSet(characterSet_),
184 		yAscent(yAscent_),
185 		yDescent(yDescent_),
186 		yInternalLeading(yInternalLeading_) {
187 	}
188 #endif
189 	FormatAndMetrics(const FormatAndMetrics &) = delete;
190 	FormatAndMetrics(FormatAndMetrics &&) = delete;
191 	FormatAndMetrics &operator=(const FormatAndMetrics &) = delete;
192 	FormatAndMetrics &operator=(FormatAndMetrics &&) = delete;
193 
~FormatAndMetricsScintilla::FormatAndMetrics194 	~FormatAndMetrics() {
195 		if (hfont)
196 			::DeleteObject(hfont);
197 #if defined(USE_D2D)
198 		ReleaseUnknown(pTextFormat);
199 #endif
200 		extraFontFlag = 0;
201 		characterSet = 0;
202 		yAscent = 2;
203 		yDescent = 1;
204 		yInternalLeading = 0;
205 	}
206 	HFONT HFont() noexcept;
207 };
208 
HFont()209 HFONT FormatAndMetrics::HFont() noexcept {
210 	LOGFONTW lf = {};
211 #if defined(USE_D2D)
212 	if (technology == SCWIN_TECH_GDI) {
213 		if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
214 			return {};
215 		}
216 	} else {
217 		const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
218 		if (!SUCCEEDED(hr)) {
219 			return {};
220 		}
221 		lf.lfWeight = pTextFormat->GetFontWeight();
222 		lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
223 		lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
224 	}
225 #else
226 	if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
227 		return {};
228 	}
229 #endif
230 	return ::CreateFontIndirectW(&lf);
231 }
232 
233 #ifndef CLEARTYPE_QUALITY
234 #define CLEARTYPE_QUALITY 5
235 #endif
236 
PointerFromWindow(HWND hWnd)237 void *PointerFromWindow(HWND hWnd) noexcept {
238 	return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
239 }
240 
SetWindowPointer(HWND hWnd,void * ptr)241 void SetWindowPointer(HWND hWnd, void *ptr) noexcept {
242 	::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
243 }
244 
245 namespace {
246 
247 // system DPI, same for all monitor.
248 UINT uSystemDPI = USER_DEFAULT_SCREEN_DPI;
249 
250 using GetDpiForWindowSig = UINT(WINAPI *)(HWND hwnd);
251 GetDpiForWindowSig fnGetDpiForWindow = nullptr;
252 
253 HMODULE hDLLShcore {};
254 using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY);
255 GetDpiForMonitorSig fnGetDpiForMonitor = nullptr;
256 
257 using GetSystemMetricsForDpiSig = int(WINAPI *)(int nIndex, UINT dpi);
258 GetSystemMetricsForDpiSig fnGetSystemMetricsForDpi = nullptr;
259 
260 using AdjustWindowRectExForDpiSig = BOOL(WINAPI *)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
261 AdjustWindowRectExForDpiSig fnAdjustWindowRectExForDpi = nullptr;
262 
LoadDpiForWindow()263 void LoadDpiForWindow() noexcept {
264 	HMODULE user32 = ::GetModuleHandleW(L"user32.dll");
265 	fnGetDpiForWindow = DLLFunction<GetDpiForWindowSig>(user32, "GetDpiForWindow");
266 	fnGetSystemMetricsForDpi = DLLFunction<GetSystemMetricsForDpiSig>(user32, "GetSystemMetricsForDpi");
267 	fnAdjustWindowRectExForDpi = DLLFunction<AdjustWindowRectExForDpiSig>(user32, "AdjustWindowRectExForDpi");
268 
269 	using GetDpiForSystemSig = UINT(WINAPI *)(void);
270 	GetDpiForSystemSig fnGetDpiForSystem = DLLFunction<GetDpiForSystemSig>(user32, "GetDpiForSystem");
271 	if (fnGetDpiForSystem) {
272 		uSystemDPI = fnGetDpiForSystem();
273 	} else {
274 		HDC hdcMeasure = ::CreateCompatibleDC({});
275 		uSystemDPI = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
276 		::DeleteDC(hdcMeasure);
277 	}
278 
279 	if (!fnGetDpiForWindow) {
280 		hDLLShcore = ::LoadLibraryExW(L"shcore.dll", {}, LOAD_LIBRARY_SEARCH_SYSTEM32);
281 		if (hDLLShcore) {
282 			fnGetDpiForMonitor = DLLFunction<GetDpiForMonitorSig>(hDLLShcore, "GetDpiForMonitor");
283 		}
284 	}
285 }
286 
287 HINSTANCE hinstPlatformRes {};
288 
FamFromFontID(void * fid)289 FormatAndMetrics *FamFromFontID(void *fid) noexcept {
290 	return static_cast<FormatAndMetrics *>(fid);
291 }
292 
Win32MapFontQuality(int extraFontFlag)293 constexpr BYTE Win32MapFontQuality(int extraFontFlag) noexcept {
294 	switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
295 
296 		case SC_EFF_QUALITY_NON_ANTIALIASED:
297 			return NONANTIALIASED_QUALITY;
298 
299 		case SC_EFF_QUALITY_ANTIALIASED:
300 			return ANTIALIASED_QUALITY;
301 
302 		case SC_EFF_QUALITY_LCD_OPTIMIZED:
303 			return CLEARTYPE_QUALITY;
304 
305 		default:
306 			return SC_EFF_QUALITY_DEFAULT;
307 	}
308 }
309 
310 #if defined(USE_D2D)
DWriteMapFontQuality(int extraFontFlag)311 constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) noexcept {
312 	switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
313 
314 		case SC_EFF_QUALITY_NON_ANTIALIASED:
315 			return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
316 
317 		case SC_EFF_QUALITY_ANTIALIASED:
318 			return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
319 
320 		case SC_EFF_QUALITY_LCD_OPTIMIZED:
321 			return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
322 
323 		default:
324 			return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
325 	}
326 }
327 #endif
328 
SetLogFont(LOGFONTW & lf,const char * faceName,int characterSet,float size,int weight,bool italic,int extraFontFlag)329 void SetLogFont(LOGFONTW &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
330 	lf = LOGFONTW();
331 	// The negative is to allow for leading
332 	lf.lfHeight = -(std::abs(std::lround(size)));
333 	lf.lfWeight = weight;
334 	lf.lfItalic = italic ? 1 : 0;
335 	lf.lfCharSet = static_cast<BYTE>(characterSet);
336 	lf.lfQuality = Win32MapFontQuality(extraFontFlag);
337 	UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE);
338 }
339 
CreateFontFromParameters(const FontParameters & fp)340 FontID CreateFontFromParameters(const FontParameters &fp) {
341 	LOGFONTW lf;
342 	SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
343 	FontID fid = nullptr;
344 	if (fp.technology == SCWIN_TECH_GDI) {
345 		HFONT hfont = ::CreateFontIndirectW(&lf);
346 		fid = new FormatAndMetrics(hfont, fp.extraFontFlag, fp.characterSet);
347 	} else {
348 #if defined(USE_D2D)
349 		IDWriteTextFormat *pTextFormat = nullptr;
350 		const std::wstring wsFace = WStringFromUTF8(fp.faceName);
351 		const FLOAT fHeight = fp.size;
352 		const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
353 		HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr,
354 			static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
355 			style,
356 			DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
357 		if (SUCCEEDED(hr)) {
358 			pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
359 
360 			FLOAT yAscent = 1.0f;
361 			FLOAT yDescent = 1.0f;
362 			FLOAT yInternalLeading = 0.0f;
363 			IDWriteTextLayout *pTextLayout = nullptr;
364 			hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
365 					100.0f, 100.0f, &pTextLayout);
366 			if (SUCCEEDED(hr) && pTextLayout) {
367 				constexpr int maxLines = 2;
368 				DWRITE_LINE_METRICS lineMetrics[maxLines]{};
369 				UINT32 lineCount = 0;
370 				hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
371 				if (SUCCEEDED(hr)) {
372 					yAscent = lineMetrics[0].baseline;
373 					yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
374 
375 					FLOAT emHeight;
376 					hr = pTextLayout->GetFontSize(0, &emHeight);
377 					if (SUCCEEDED(hr)) {
378 						yInternalLeading = lineMetrics[0].height - emHeight;
379 					}
380 				}
381 				ReleaseUnknown(pTextLayout);
382 				pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
383 			}
384 			fid = new FormatAndMetrics(pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading);
385 		}
386 #endif
387 	}
388 	return fid;
389 }
390 
391 }
392 
Font()393 Font::Font() noexcept : fid{} {
394 }
395 
~Font()396 Font::~Font() {
397 }
398 
Create(const FontParameters & fp)399 void Font::Create(const FontParameters &fp) {
400 	Release();
401 	if (fp.faceName)
402 		fid = CreateFontFromParameters(fp);
403 }
404 
Release()405 void Font::Release() {
406 	if (fid)
407 		delete FamFromFontID(fid);
408 	fid = nullptr;
409 }
410 
411 // Buffer to hold strings and string position arrays without always allocating on heap.
412 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
413 // when less than safe size otherwise allocate on heap and free automatically.
414 template<typename T, int lengthStandard>
415 class VarBuffer {
416 	T bufferStandard[lengthStandard];
417 public:
418 	T *buffer;
VarBuffer(size_t length)419 	explicit VarBuffer(size_t length) : buffer(nullptr) {
420 		if (length > lengthStandard) {
421 			buffer = new T[length];
422 		} else {
423 			buffer = bufferStandard;
424 		}
425 	}
426 	// Deleted so VarBuffer objects can not be copied.
427 	VarBuffer(const VarBuffer &) = delete;
428 	VarBuffer(VarBuffer &&) = delete;
429 	VarBuffer &operator=(const VarBuffer &) = delete;
430 	VarBuffer &operator=(VarBuffer &&) = delete;
431 
~VarBuffer()432 	~VarBuffer() {
433 		if (buffer != bufferStandard) {
434 			delete []buffer;
435 			buffer = nullptr;
436 		}
437 	}
438 };
439 
440 constexpr int stackBufferLength = 1000;
441 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
442 public:
443 	int tlen;	// Using int instead of size_t as most Win32 APIs take int.
TextWide(std::string_view text,bool unicodeMode,int codePage=0)444 	TextWide(std::string_view text, bool unicodeMode, int codePage=0) :
445 		VarBuffer<wchar_t, stackBufferLength>(text.length()) {
446 		if (unicodeMode) {
447 			tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length()));
448 		} else {
449 			// Support Asian string display in 9x English
450 			tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()),
451 				buffer, static_cast<int>(text.length()));
452 		}
453 	}
454 };
455 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
456 
DpiForWindow(WindowID wid)457 UINT DpiForWindow(WindowID wid) noexcept {
458 	if (fnGetDpiForWindow) {
459 		return fnGetDpiForWindow(HwndFromWindowID(wid));
460 	}
461 	if (fnGetDpiForMonitor) {
462 		HMONITOR hMonitor = ::MonitorFromWindow(HwndFromWindowID(wid), MONITOR_DEFAULTTONEAREST);
463 		UINT dpiX = 0;
464 		UINT dpiY = 0;
465 		if (fnGetDpiForMonitor(hMonitor, 0 /*MDT_EFFECTIVE_DPI*/, &dpiX, &dpiY) == S_OK) {
466 			return dpiY;
467 		}
468 	}
469 	return uSystemDPI;
470 }
471 
SystemMetricsForDpi(int nIndex,UINT dpi)472 int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept {
473 	if (fnGetSystemMetricsForDpi) {
474 		return fnGetSystemMetricsForDpi(nIndex, dpi);
475 	}
476 
477 	int value = ::GetSystemMetrics(nIndex);
478 	value = (dpi == uSystemDPI) ? value : ::MulDiv(value, dpi, uSystemDPI);
479 	return value;
480 }
481 
482 class SurfaceGDI : public Surface {
483 	bool unicodeMode=false;
484 	HDC hdc{};
485 	bool hdcOwned=false;
486 	HPEN pen{};
487 	HPEN penOld{};
488 	HBRUSH brush{};
489 	HBRUSH brushOld{};
490 	HFONT fontOld{};
491 	HBITMAP bitmap{};
492 	HBITMAP bitmapOld{};
493 
494 	int logPixelsY = USER_DEFAULT_SCREEN_DPI;
495 
496 	int maxWidthMeasure = INT_MAX;
497 	// There appears to be a 16 bit string length limit in GDI on NT.
498 	int maxLenText = 65535;
499 
500 	int codePage = 0;
501 
502 	void BrushColour(ColourDesired back) noexcept;
503 	void SetFont(const Font &font_) noexcept;
504 	void Clear() noexcept;
505 
506 public:
507 	SurfaceGDI() noexcept;
508 	// Deleted so SurfaceGDI objects can not be copied.
509 	SurfaceGDI(const SurfaceGDI &) = delete;
510 	SurfaceGDI(SurfaceGDI &&) = delete;
511 	SurfaceGDI &operator=(const SurfaceGDI &) = delete;
512 	SurfaceGDI &operator=(SurfaceGDI &&) = delete;
513 
514 	~SurfaceGDI() noexcept override;
515 
516 	void Init(WindowID wid) override;
517 	void Init(SurfaceID sid, WindowID wid) override;
518 	void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override;
519 
520 	void Release() override;
521 	bool Initialised() override;
522 	void PenColour(ColourDesired fore) override;
523 	int LogPixelsY() override;
524 	int DeviceHeightFont(int points) override;
525 	void MoveTo(int x_, int y_) override;
526 	void LineTo(int x_, int y_) override;
527 	void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override;
528 	void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override;
529 	void FillRectangle(PRectangle rc, ColourDesired back) override;
530 	void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
531 	void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override;
532 	void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
533 		ColourDesired outline, int alphaOutline, int flags) override;
534 	void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
535 	void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
536 	void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override;
537 	void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
538 
539 	std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
540 
541 	void DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
542 	void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
543 	void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
544 	void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override;
545 	void MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) override;
546 	XYPOSITION WidthText(Font &font_, std::string_view text) override;
547 	XYPOSITION Ascent(Font &font_) override;
548 	XYPOSITION Descent(Font &font_) override;
549 	XYPOSITION InternalLeading(Font &font_) override;
550 	XYPOSITION Height(Font &font_) override;
551 	XYPOSITION AverageCharWidth(Font &font_) override;
552 
553 	void SetClip(PRectangle rc) override;
554 	void FlushCachedState() override;
555 
556 	void SetUnicodeMode(bool unicodeMode_) override;
557 	void SetDBCSMode(int codePage_) override;
558 	void SetBidiR2L(bool bidiR2L_) override;
559 };
560 
SurfaceGDI()561 SurfaceGDI::SurfaceGDI() noexcept {
562 }
563 
~SurfaceGDI()564 SurfaceGDI::~SurfaceGDI() noexcept {
565 	Clear();
566 }
567 
Clear()568 void SurfaceGDI::Clear() noexcept {
569 	if (penOld) {
570 		::SelectObject(hdc, penOld);
571 		::DeleteObject(pen);
572 		penOld = {};
573 	}
574 	pen = {};
575 	if (brushOld) {
576 		::SelectObject(hdc, brushOld);
577 		::DeleteObject(brush);
578 		brushOld = {};
579 	}
580 	brush = {};
581 	if (fontOld) {
582 		// Fonts are not deleted as they are owned by a Font object
583 		::SelectObject(hdc, fontOld);
584 		fontOld = {};
585 	}
586 	if (bitmapOld) {
587 		::SelectObject(hdc, bitmapOld);
588 		::DeleteObject(bitmap);
589 		bitmapOld = {};
590 	}
591 	bitmap = {};
592 	if (hdcOwned) {
593 		::DeleteDC(hdc);
594 		hdc = {};
595 		hdcOwned = false;
596 	}
597 }
598 
Release()599 void SurfaceGDI::Release() {
600 	Clear();
601 }
602 
Initialised()603 bool SurfaceGDI::Initialised() {
604 	return hdc != 0;
605 }
606 
Init(WindowID wid)607 void SurfaceGDI::Init(WindowID wid) {
608 	Release();
609 	hdc = ::CreateCompatibleDC({});
610 	hdcOwned = true;
611 	::SetTextAlign(hdc, TA_BASELINE);
612 	logPixelsY = DpiForWindow(wid);
613 }
614 
Init(SurfaceID sid,WindowID wid)615 void SurfaceGDI::Init(SurfaceID sid, WindowID wid) {
616 	Release();
617 	hdc = static_cast<HDC>(sid);
618 	::SetTextAlign(hdc, TA_BASELINE);
619 	// Windows on screen are scaled but printers are not.
620 	const bool printing = ::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY;
621 	logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiForWindow(wid);
622 }
623 
InitPixMap(int width,int height,Surface * surface_,WindowID wid)624 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
625 	Release();
626 	SurfaceGDI *psurfOther = dynamic_cast<SurfaceGDI *>(surface_);
627 	// Should only ever be called with a SurfaceGDI, not a SurfaceD2D
628 	PLATFORM_ASSERT(psurfOther);
629 	hdc = ::CreateCompatibleDC(psurfOther->hdc);
630 	hdcOwned = true;
631 	bitmap = ::CreateCompatibleBitmap(psurfOther->hdc, width, height);
632 	bitmapOld = SelectBitmap(hdc, bitmap);
633 	::SetTextAlign(hdc, TA_BASELINE);
634 	SetUnicodeMode(psurfOther->unicodeMode);
635 	SetDBCSMode(psurfOther->codePage);
636 	logPixelsY = DpiForWindow(wid);
637 }
638 
PenColour(ColourDesired fore)639 void SurfaceGDI::PenColour(ColourDesired fore) {
640 	if (pen) {
641 		::SelectObject(hdc, penOld);
642 		::DeleteObject(pen);
643 		pen = {};
644 		penOld = {};
645 	}
646 	pen = ::CreatePen(0,1,fore.AsInteger());
647 	penOld = SelectPen(hdc, pen);
648 }
649 
BrushColour(ColourDesired back)650 void SurfaceGDI::BrushColour(ColourDesired back) noexcept {
651 	if (brush) {
652 		::SelectObject(hdc, brushOld);
653 		::DeleteObject(brush);
654 		brush = {};
655 		brushOld = {};
656 	}
657 	brush = ::CreateSolidBrush(back.AsInteger());
658 	brushOld = SelectBrush(hdc, brush);
659 }
660 
SetFont(const Font & font_)661 void SurfaceGDI::SetFont(const Font &font_) noexcept {
662 	const FormatAndMetrics *pfm = FamFromFontID(font_.GetID());
663 	PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
664 	if (fontOld) {
665 		SelectFont(hdc, pfm->hfont);
666 	} else {
667 		fontOld = SelectFont(hdc, pfm->hfont);
668 	}
669 }
670 
LogPixelsY()671 int SurfaceGDI::LogPixelsY() {
672 	return logPixelsY;
673 }
674 
DeviceHeightFont(int points)675 int SurfaceGDI::DeviceHeightFont(int points) {
676 	return ::MulDiv(points, LogPixelsY(), 72);
677 }
678 
MoveTo(int x_,int y_)679 void SurfaceGDI::MoveTo(int x_, int y_) {
680 	::MoveToEx(hdc, x_, y_, nullptr);
681 }
682 
LineTo(int x_,int y_)683 void SurfaceGDI::LineTo(int x_, int y_) {
684 	::LineTo(hdc, x_, y_);
685 }
686 
Polygon(Point * pts,size_t npts,ColourDesired fore,ColourDesired back)687 void SurfaceGDI::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) {
688 	PenColour(fore);
689 	BrushColour(back);
690 	std::vector<POINT> outline;
691 	std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
692 	::Polygon(hdc, outline.data(), static_cast<int>(npts));
693 }
694 
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)695 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
696 	PenColour(fore);
697 	BrushColour(back);
698 	const RECT rcw = RectFromPRectangle(rc);
699 	::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
700 }
701 
FillRectangle(PRectangle rc,ColourDesired back)702 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
703 	// Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
704 	// There is no need to allocate a brush either.
705 	const RECT rcw = RectFromPRectangle(rc);
706 	::SetBkColor(hdc, back.AsInteger());
707 	::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr);
708 }
709 
FillRectangle(PRectangle rc,Surface & surfacePattern)710 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
711 	HBRUSH br;
712 	if (SurfaceGDI *psgdi = dynamic_cast<SurfaceGDI *>(&surfacePattern); psgdi && psgdi->bitmap) {
713 		br = ::CreatePatternBrush(psgdi->bitmap);
714 	} else {	// Something is wrong so display in red
715 		br = ::CreateSolidBrush(RGB(0xff, 0, 0));
716 	}
717 	const RECT rcw = RectFromPRectangle(rc);
718 	::FillRect(hdc, &rcw, br);
719 	::DeleteObject(br);
720 }
721 
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)722 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
723 	PenColour(fore);
724 	BrushColour(back);
725 	const RECT rcw = RectFromPRectangle(rc);
726 	::RoundRect(hdc,
727 		rcw.left + 1, rcw.top,
728 		rcw.right - 1, rcw.bottom,
729 		8, 8);
730 }
731 
732 namespace {
733 
dwordFromBGRA(byte b,byte g,byte r,byte a)734 constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept {
735 	return (a << 24) | (r << 16) | (g << 8) | b;
736 }
737 
AlphaScaled(unsigned char component,unsigned int alpha)738 constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept {
739 	return static_cast<byte>(component * alpha / 255);
740 }
741 
dwordMultiplied(ColourDesired colour,unsigned int alpha)742 constexpr DWORD dwordMultiplied(ColourDesired colour, unsigned int alpha) noexcept {
743 	return dwordFromBGRA(
744 		AlphaScaled(colour.GetBlue(), alpha),
745 		AlphaScaled(colour.GetGreen(), alpha),
746 		AlphaScaled(colour.GetRed(), alpha),
747 		static_cast<byte>(alpha));
748 }
749 
750 class DIBSection {
751 	HDC hMemDC {};
752 	HBITMAP hbmMem {};
753 	HBITMAP hbmOld {};
754 	SIZE size {};
755 	DWORD *pixels = nullptr;
756 public:
757 	DIBSection(HDC hdc, SIZE size_) noexcept;
758 	// Deleted so DIBSection objects can not be copied.
759 	DIBSection(const DIBSection&) = delete;
760 	DIBSection(DIBSection&&) = delete;
761 	DIBSection &operator=(const DIBSection&) = delete;
762 	DIBSection &operator=(DIBSection&&) = delete;
763 	~DIBSection() noexcept;
operator bool() const764 	operator bool() const noexcept {
765 		return hMemDC && hbmMem && pixels;
766 	}
Pixels() const767 	DWORD *Pixels() const noexcept {
768 		return pixels;
769 	}
Bytes() const770 	unsigned char *Bytes() const noexcept {
771 		return reinterpret_cast<unsigned char *>(pixels);
772 	}
DC() const773 	HDC DC() const noexcept {
774 		return hMemDC;
775 	}
SetPixel(LONG x,LONG y,DWORD value)776 	void SetPixel(LONG x, LONG y, DWORD value) noexcept {
777 		PLATFORM_ASSERT(x >= 0);
778 		PLATFORM_ASSERT(y >= 0);
779 		PLATFORM_ASSERT(x < size.cx);
780 		PLATFORM_ASSERT(y < size.cy);
781 		pixels[y * size.cx + x] = value;
782 	}
783 	void SetSymmetric(LONG x, LONG y, DWORD value) noexcept;
784 };
785 
DIBSection(HDC hdc,SIZE size_)786 DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept {
787 	hMemDC = ::CreateCompatibleDC(hdc);
788 	if (!hMemDC) {
789 		return;
790 	}
791 
792 	size = size_;
793 
794 	// -size.y makes bitmap start from top
795 	const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0},
796 		{{0, 0, 0, 0}} };
797 	void *image = nullptr;
798 	hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, &image, {}, 0);
799 	if (!hbmMem || !image) {
800 		return;
801 	}
802 	pixels = static_cast<DWORD *>(image);
803 	hbmOld = SelectBitmap(hMemDC, hbmMem);
804 }
805 
~DIBSection()806 DIBSection::~DIBSection() noexcept {
807 	if (hbmOld) {
808 		SelectBitmap(hMemDC, hbmOld);
809 		hbmOld = {};
810 	}
811 	if (hbmMem) {
812 		::DeleteObject(hbmMem);
813 		hbmMem = {};
814 	}
815 	if (hMemDC) {
816 		::DeleteDC(hMemDC);
817 		hMemDC = {};
818 	}
819 }
820 
SetSymmetric(LONG x,LONG y,DWORD value)821 void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept {
822 	// Plot a point symmetrically to all 4 quadrants
823 	const LONG xSymmetric = size.cx - 1 - x;
824 	const LONG ySymmetric = size.cy - 1 - y;
825 	SetPixel(x, y, value);
826 	SetPixel(xSymmetric, y, value);
827 	SetPixel(x, ySymmetric, value);
828 	SetPixel(xSymmetric, ySymmetric, value);
829 }
830 
Proportional(unsigned char a,unsigned char b,float t)831 constexpr unsigned int Proportional(unsigned char a, unsigned char b, float t) noexcept {
832 	return static_cast<unsigned int>(a + t * (b - a));
833 }
834 
Proportional(ColourAlpha a,ColourAlpha b,float t)835 ColourAlpha Proportional(ColourAlpha a, ColourAlpha b, float t) noexcept {
836 	return ColourAlpha(
837 		Proportional(a.GetRed(), b.GetRed(), t),
838 		Proportional(a.GetGreen(), b.GetGreen(), t),
839 		Proportional(a.GetBlue(), b.GetBlue(), t),
840 		Proportional(a.GetAlpha(), b.GetAlpha(), t));
841 }
842 
GradientValue(const std::vector<ColourStop> & stops,float proportion)843 ColourAlpha GradientValue(const std::vector<ColourStop> &stops, float proportion) noexcept {
844 	for (size_t stop = 0; stop < stops.size() - 1; stop++) {
845 		// Loop through each pair of stops
846 		const float positionStart = stops[stop].position;
847 		const float positionEnd = stops[stop + 1].position;
848 		if ((proportion >= positionStart) && (proportion <= positionEnd)) {
849 			const float proportionInPair = (proportion - positionStart) /
850 				(positionEnd - positionStart);
851 			return Proportional(stops[stop].colour, stops[stop + 1].colour, proportionInPair);
852 		}
853 	}
854 	// Loop should always find a value
855 	return ColourAlpha();
856 }
857 
SizeOfRect(RECT rc)858 constexpr SIZE SizeOfRect(RECT rc) noexcept {
859 	return { rc.right - rc.left, rc.bottom - rc.top };
860 }
861 
862 constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
863 
864 }
865 
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int)866 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
867 		ColourDesired outline, int alphaOutline, int /* flags*/ ) {
868 	const RECT rcw = RectFromPRectangle(rc);
869 	const SIZE size = SizeOfRect(rcw);
870 
871 	if (size.cx > 0) {
872 
873 		DIBSection section(hdc, size);
874 
875 		if (section) {
876 
877 			// Ensure not distorted too much by corners when small
878 			const LONG corner = std::min<LONG>(cornerSize, (std::min(size.cx, size.cy) / 2) - 2);
879 
880 			constexpr DWORD valEmpty = dwordFromBGRA(0,0,0,0);
881 			const DWORD valFill = dwordMultiplied(fill, alphaFill);
882 			const DWORD valOutline = dwordMultiplied(outline, alphaOutline);
883 
884 			// Draw a framed rectangle
885 			for (int y=0; y<size.cy; y++) {
886 				for (int x=0; x<size.cx; x++) {
887 					if ((x==0) || (x==size.cx-1) || (y == 0) || (y == size.cy -1)) {
888 						section.SetPixel(x, y, valOutline);
889 					} else {
890 						section.SetPixel(x, y, valFill);
891 					}
892 				}
893 			}
894 
895 			// Make the corners transparent
896 			for (LONG c=0; c<corner; c++) {
897 				for (LONG x=0; x<c+1; x++) {
898 					section.SetSymmetric(x, c - x, valEmpty);
899 				}
900 			}
901 
902 			// Draw the corner frame pieces
903 			for (LONG x=1; x<corner; x++) {
904 				section.SetSymmetric(x, corner - x, valOutline);
905 			}
906 
907 			AlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
908 		}
909 	} else {
910 		BrushColour(outline);
911 		FrameRect(hdc, &rcw, brush);
912 	}
913 }
914 
GradientRectangle(PRectangle rc,const std::vector<ColourStop> & stops,GradientOptions options)915 void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
916 
917 	const RECT rcw = RectFromPRectangle(rc);
918 	const SIZE size = SizeOfRect(rcw);
919 
920 	DIBSection section(hdc, size);
921 
922 	if (section) {
923 
924 		if (options == GradientOptions::topToBottom) {
925 			for (LONG y = 0; y < size.cy; y++) {
926 				// Find y/height proportional colour
927 				const float proportion = y / (rc.Height() - 1.0f);
928 				const ColourAlpha mixed = GradientValue(stops, proportion);
929 				const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha());
930 				for (LONG x = 0; x < size.cx; x++) {
931 					section.SetPixel(x, y, valFill);
932 				}
933 			}
934 		} else {
935 			for (LONG x = 0; x < size.cx; x++) {
936 				// Find x/width proportional colour
937 				const float proportion = x / (rc.Width() - 1.0f);
938 				const ColourAlpha mixed = GradientValue(stops, proportion);
939 				const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha());
940 				for (LONG y = 0; y < size.cy; y++) {
941 					section.SetPixel(x, y, valFill);
942 				}
943 			}
944 		}
945 
946 		AlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
947 	}
948 }
949 
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)950 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
951 	if (rc.Width() > 0) {
952 		if (rc.Width() > width)
953 			rc.left += std::floor((rc.Width() - width) / 2);
954 		rc.right = rc.left + width;
955 		if (rc.Height() > height)
956 			rc.top += std::floor((rc.Height() - height) / 2);
957 		rc.bottom = rc.top + height;
958 
959 		const SIZE size { width, height };
960 		DIBSection section(hdc, size);
961 		if (section) {
962 			RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, width * height);
963 			AlphaBlend(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
964 				static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), section.DC(),
965 				0, 0, width, height, mergeAlpha);
966 		}
967 	}
968 }
969 
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)970 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
971 	PenColour(fore);
972 	BrushColour(back);
973 	const RECT rcw = RectFromPRectangle(rc);
974 	::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
975 }
976 
Copy(PRectangle rc,Point from,Surface & surfaceSource)977 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
978 	::BitBlt(hdc,
979 		static_cast<int>(rc.left), static_cast<int>(rc.top),
980 		static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
981 		dynamic_cast<SurfaceGDI &>(surfaceSource).hdc,
982 		static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
983 }
984 
Layout(const IScreenLine *)985 std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) {
986 	return {};
987 }
988 
989 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
990 
DrawTextCommon(PRectangle rc,const Font & font_,XYPOSITION ybase,std::string_view text,UINT fuOptions)991 void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
992 	SetFont(font_);
993 	const RECT rcw = RectFromPRectangle(rc);
994 	const int x = static_cast<int>(rc.left);
995 	const int yBaseInt = static_cast<int>(ybase);
996 
997 	if (unicodeMode) {
998 		const TextWide tbuf(text, unicodeMode, codePage);
999 		::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
1000 	} else {
1001 		::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast<UINT>(text.length()), nullptr);
1002 	}
1003 }
1004 
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)1005 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
1006 	ColourDesired fore, ColourDesired back) {
1007 	::SetTextColor(hdc, fore.AsInteger());
1008 	::SetBkColor(hdc, back.AsInteger());
1009 	DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE);
1010 }
1011 
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)1012 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
1013 	ColourDesired fore, ColourDesired back) {
1014 	::SetTextColor(hdc, fore.AsInteger());
1015 	::SetBkColor(hdc, back.AsInteger());
1016 	DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
1017 }
1018 
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)1019 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
1020 	ColourDesired fore) {
1021 	// Avoid drawing spaces in transparent mode
1022 	for (const char ch : text) {
1023 		if (ch != ' ') {
1024 			::SetTextColor(hdc, fore.AsInteger());
1025 			::SetBkMode(hdc, TRANSPARENT);
1026 			DrawTextCommon(rc, font_, ybase, text, 0);
1027 			::SetBkMode(hdc, OPAQUE);
1028 			return;
1029 		}
1030 	}
1031 }
1032 
WidthText(Font & font_,std::string_view text)1033 XYPOSITION SurfaceGDI::WidthText(Font &font_, std::string_view text) {
1034 	SetFont(font_);
1035 	SIZE sz={0,0};
1036 	if (!unicodeMode) {
1037 		::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz);
1038 	} else {
1039 		const TextWide tbuf(text, unicodeMode, codePage);
1040 		::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
1041 	}
1042 	return static_cast<XYPOSITION>(sz.cx);
1043 }
1044 
MeasureWidths(Font & font_,std::string_view text,XYPOSITION * positions)1045 void SurfaceGDI::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) {
1046 	// Zero positions to avoid random behaviour on failure.
1047 	std::fill(positions, positions + text.length(), 0.0f);
1048 	SetFont(font_);
1049 	SIZE sz={0,0};
1050 	int fit = 0;
1051 	int i = 0;
1052 	const int len = static_cast<int>(text.length());
1053 	if (unicodeMode) {
1054 		const TextWide tbuf(text, unicodeMode, codePage);
1055 		TextPositionsI poses(tbuf.tlen);
1056 		if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1057 			// Failure
1058 			return;
1059 		}
1060 		// Map the widths given for UTF-16 characters back onto the UTF-8 input string
1061 		for (int ui = 0; ui < fit; ui++) {
1062 			const unsigned char uch = text[i];
1063 			const unsigned int byteCount = UTF8BytesOfLead[uch];
1064 			if (byteCount == 4) {	// Non-BMP
1065 				ui++;
1066 			}
1067 			for (unsigned int bytePos=0; (bytePos<byteCount) && (i<len); bytePos++) {
1068 				positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
1069 			}
1070 		}
1071 	} else {
1072 		TextPositionsI poses(len);
1073 		if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1074 			// Eeek - a NULL DC or other foolishness could cause this.
1075 			return;
1076 		}
1077 		while (i<fit) {
1078 			positions[i] = static_cast<XYPOSITION>(poses.buffer[i]);
1079 			i++;
1080 		}
1081 	}
1082 	// If any positions not filled in then use the last position for them
1083 	const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
1084 	std::fill(positions+i, positions + text.length(), lastPos);
1085 }
1086 
Ascent(Font & font_)1087 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1088 	SetFont(font_);
1089 	TEXTMETRIC tm;
1090 	::GetTextMetrics(hdc, &tm);
1091 	return static_cast<XYPOSITION>(tm.tmAscent);
1092 }
1093 
Descent(Font & font_)1094 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1095 	SetFont(font_);
1096 	TEXTMETRIC tm;
1097 	::GetTextMetrics(hdc, &tm);
1098 	return static_cast<XYPOSITION>(tm.tmDescent);
1099 }
1100 
InternalLeading(Font & font_)1101 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1102 	SetFont(font_);
1103 	TEXTMETRIC tm;
1104 	::GetTextMetrics(hdc, &tm);
1105 	return static_cast<XYPOSITION>(tm.tmInternalLeading);
1106 }
1107 
Height(Font & font_)1108 XYPOSITION SurfaceGDI::Height(Font &font_) {
1109 	SetFont(font_);
1110 	TEXTMETRIC tm;
1111 	::GetTextMetrics(hdc, &tm);
1112 	return static_cast<XYPOSITION>(tm.tmHeight);
1113 }
1114 
AverageCharWidth(Font & font_)1115 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1116 	SetFont(font_);
1117 	TEXTMETRIC tm;
1118 	::GetTextMetrics(hdc, &tm);
1119 	return static_cast<XYPOSITION>(tm.tmAveCharWidth);
1120 }
1121 
SetClip(PRectangle rc)1122 void SurfaceGDI::SetClip(PRectangle rc) {
1123 	::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
1124 		static_cast<int>(rc.right), static_cast<int>(rc.bottom));
1125 }
1126 
FlushCachedState()1127 void SurfaceGDI::FlushCachedState() {
1128 	pen = {};
1129 	brush = {};
1130 }
1131 
SetUnicodeMode(bool unicodeMode_)1132 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1133 	unicodeMode=unicodeMode_;
1134 }
1135 
SetDBCSMode(int codePage_)1136 void SurfaceGDI::SetDBCSMode(int codePage_) {
1137 	// No action on window as automatically handled by system.
1138 	codePage = codePage_;
1139 }
1140 
SetBidiR2L(bool)1141 void SurfaceGDI::SetBidiR2L(bool) {
1142 }
1143 
1144 #if defined(USE_D2D)
1145 
1146 namespace {
1147 
RectangleFromPRectangle(PRectangle rc)1148 constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept {
1149 	return { rc.left, rc.top, rc.right, rc.bottom };
1150 }
1151 
1152 }
1153 
1154 class BlobInline;
1155 
1156 class SurfaceD2D : public Surface {
1157 	bool unicodeMode;
1158 	int x, y;
1159 
1160 	int codePage;
1161 	int codePageText;
1162 
1163 	ID2D1RenderTarget *pRenderTarget;
1164 	ID2D1BitmapRenderTarget *pBitmapRenderTarget;
1165 	bool ownRenderTarget;
1166 	int clipsActive;
1167 
1168 	IDWriteTextFormat *pTextFormat;
1169 	FLOAT yAscent;
1170 	FLOAT yDescent;
1171 	FLOAT yInternalLeading;
1172 
1173 	ID2D1SolidColorBrush *pBrush;
1174 
1175 	int logPixelsY;
1176 
1177 	void Clear() noexcept;
1178 	void SetFont(const Font &font_) noexcept;
1179 	HRESULT GetBitmap(ID2D1Bitmap **ppBitmap);
1180 
1181 public:
1182 	SurfaceD2D() noexcept;
1183 	// Deleted so SurfaceD2D objects can not be copied.
1184 	SurfaceD2D(const SurfaceD2D &) = delete;
1185 	SurfaceD2D(SurfaceD2D &&) = delete;
1186 	SurfaceD2D &operator=(const SurfaceD2D &) = delete;
1187 	SurfaceD2D &operator=(SurfaceD2D &&) = delete;
1188 	~SurfaceD2D() override;
1189 
1190 	void SetScale(WindowID wid) noexcept;
1191 	void Init(WindowID wid) override;
1192 	void Init(SurfaceID sid, WindowID wid) override;
1193 	void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override;
1194 
1195 	void Release() override;
1196 	bool Initialised() override;
1197 
1198 	HRESULT FlushDrawing();
1199 
1200 	void PenColour(ColourDesired fore) override;
1201 	void D2DPenColour(ColourDesired fore, int alpha=255);
1202 	int LogPixelsY() override;
1203 	int DeviceHeightFont(int points) override;
1204 	void MoveTo(int x_, int y_) override;
1205 	void LineTo(int x_, int y_) override;
1206 	void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override;
1207 	void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override;
1208 	void FillRectangle(PRectangle rc, ColourDesired back) override;
1209 	void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
1210 	void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override;
1211 	void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1212 		ColourDesired outline, int alphaOutline, int flags) override;
1213 	void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
1214 	void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
1215 	void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override;
1216 	void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
1217 
1218 	std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
1219 
1220 	void DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
1221 	void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
1222 	void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override;
1223 	void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override;
1224 	void MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) override;
1225 	XYPOSITION WidthText(Font &font_, std::string_view text) override;
1226 	XYPOSITION Ascent(Font &font_) override;
1227 	XYPOSITION Descent(Font &font_) override;
1228 	XYPOSITION InternalLeading(Font &font_) override;
1229 	XYPOSITION Height(Font &font_) override;
1230 	XYPOSITION AverageCharWidth(Font &font_) override;
1231 
1232 	void SetClip(PRectangle rc) override;
1233 	void FlushCachedState() override;
1234 
1235 	void SetUnicodeMode(bool unicodeMode_) override;
1236 	void SetDBCSMode(int codePage_) override;
1237 	void SetBidiR2L(bool bidiR2L_) override;
1238 };
1239 
SurfaceD2D()1240 SurfaceD2D::SurfaceD2D() noexcept :
1241 	unicodeMode(false),
1242 	x(0), y(0) {
1243 
1244 	codePage = 0;
1245 	codePageText = 0;
1246 
1247 	pRenderTarget = nullptr;
1248 	pBitmapRenderTarget = nullptr;
1249 	ownRenderTarget = false;
1250 	clipsActive = 0;
1251 
1252 	// From selected font
1253 	pTextFormat = nullptr;
1254 	yAscent = 2;
1255 	yDescent = 1;
1256 	yInternalLeading = 0;
1257 
1258 	pBrush = nullptr;
1259 
1260 	logPixelsY = USER_DEFAULT_SCREEN_DPI;
1261 }
1262 
~SurfaceD2D()1263 SurfaceD2D::~SurfaceD2D() {
1264 	Clear();
1265 }
1266 
Clear()1267 void SurfaceD2D::Clear() noexcept {
1268 	ReleaseUnknown(pBrush);
1269 	if (pRenderTarget) {
1270 		while (clipsActive) {
1271 			pRenderTarget->PopAxisAlignedClip();
1272 			clipsActive--;
1273 		}
1274 		if (ownRenderTarget) {
1275 			pRenderTarget->EndDraw();
1276 			ReleaseUnknown(pRenderTarget);
1277 			ownRenderTarget = false;
1278 		}
1279 		pRenderTarget = nullptr;
1280 	}
1281 	pBitmapRenderTarget = nullptr;
1282 }
1283 
Release()1284 void SurfaceD2D::Release() {
1285 	Clear();
1286 }
1287 
SetScale(WindowID wid)1288 void SurfaceD2D::SetScale(WindowID wid) noexcept {
1289 	logPixelsY = DpiForWindow(wid);
1290 }
1291 
Initialised()1292 bool SurfaceD2D::Initialised() {
1293 	return pRenderTarget != nullptr;
1294 }
1295 
FlushDrawing()1296 HRESULT SurfaceD2D::FlushDrawing() {
1297 	return pRenderTarget->Flush();
1298 }
1299 
Init(WindowID wid)1300 void SurfaceD2D::Init(WindowID wid) {
1301 	Release();
1302 	SetScale(wid);
1303 }
1304 
Init(SurfaceID sid,WindowID wid)1305 void SurfaceD2D::Init(SurfaceID sid, WindowID wid) {
1306 	Release();
1307 	SetScale(wid);
1308 	pRenderTarget = static_cast<ID2D1RenderTarget *>(sid);
1309 }
1310 
InitPixMap(int width,int height,Surface * surface_,WindowID wid)1311 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID wid) {
1312 	Release();
1313 	SetScale(wid);
1314 	SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(surface_);
1315 	// Should only ever be called with a SurfaceD2D, not a SurfaceGDI
1316 	PLATFORM_ASSERT(psurfOther);
1317 	const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height));
1318 	D2D1_PIXEL_FORMAT desiredFormat;
1319 #ifdef __MINGW32__
1320 	desiredFormat.format = DXGI_FORMAT_UNKNOWN;
1321 #else
1322 	desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1323 #endif
1324 	desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1325 	const HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1326 		&desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pBitmapRenderTarget);
1327 	if (SUCCEEDED(hr)) {
1328 		pRenderTarget = pBitmapRenderTarget;
1329 		pRenderTarget->BeginDraw();
1330 		ownRenderTarget = true;
1331 	}
1332 	SetUnicodeMode(psurfOther->unicodeMode);
1333 	SetDBCSMode(psurfOther->codePage);
1334 }
1335 
GetBitmap(ID2D1Bitmap ** ppBitmap)1336 HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) {
1337 	PLATFORM_ASSERT(pBitmapRenderTarget);
1338 	return pBitmapRenderTarget->GetBitmap(ppBitmap);
1339 }
1340 
PenColour(ColourDesired fore)1341 void SurfaceD2D::PenColour(ColourDesired fore) {
1342 	D2DPenColour(fore);
1343 }
1344 
D2DPenColour(ColourDesired fore,int alpha)1345 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1346 	if (pRenderTarget) {
1347 		D2D_COLOR_F col;
1348 		col.r = fore.GetRedComponent();
1349 		col.g = fore.GetGreenComponent();
1350 		col.b = fore.GetBlueComponent();
1351 		col.a = alpha / 255.0f;
1352 		if (pBrush) {
1353 			pBrush->SetColor(col);
1354 		} else {
1355 			const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1356 			if (!SUCCEEDED(hr)) {
1357 				ReleaseUnknown(pBrush);
1358 			}
1359 		}
1360 	}
1361 }
1362 
SetFont(const Font & font_)1363 void SurfaceD2D::SetFont(const Font &font_) noexcept {
1364 	const FormatAndMetrics *pfm = FamFromFontID(font_.GetID());
1365 	PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1366 	pTextFormat = pfm->pTextFormat;
1367 	yAscent = pfm->yAscent;
1368 	yDescent = pfm->yDescent;
1369 	yInternalLeading = pfm->yInternalLeading;
1370 	codePageText = codePage;
1371 	if (!unicodeMode && pfm->characterSet) {
1372 		codePageText = Scintilla::CodePageFromCharSet(pfm->characterSet, codePage);
1373 	}
1374 	if (pRenderTarget) {
1375 		D2D1_TEXT_ANTIALIAS_MODE aaMode;
1376 		aaMode = DWriteMapFontQuality(pfm->extraFontFlag);
1377 
1378 		if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams)
1379 			pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams);
1380 		else if (defaultRenderingParams)
1381 			pRenderTarget->SetTextRenderingParams(defaultRenderingParams);
1382 
1383 		pRenderTarget->SetTextAntialiasMode(aaMode);
1384 	}
1385 }
1386 
LogPixelsY()1387 int SurfaceD2D::LogPixelsY() {
1388 	return logPixelsY;
1389 }
1390 
DeviceHeightFont(int points)1391 int SurfaceD2D::DeviceHeightFont(int points) {
1392 	return ::MulDiv(points, LogPixelsY(), 72);
1393 }
1394 
MoveTo(int x_,int y_)1395 void SurfaceD2D::MoveTo(int x_, int y_) {
1396 	x = x_;
1397 	y = y_;
1398 }
1399 
Delta(int difference)1400 static constexpr int Delta(int difference) noexcept {
1401 	if (difference < 0)
1402 		return -1;
1403 	else if (difference > 0)
1404 		return 1;
1405 	else
1406 		return 0;
1407 }
1408 
LineTo(int x_,int y_)1409 void SurfaceD2D::LineTo(int x_, int y_) {
1410 	if (pRenderTarget) {
1411 		const int xDiff = x_ - x;
1412 		const int xDelta = Delta(xDiff);
1413 		const int yDiff = y_ - y;
1414 		const int yDelta = Delta(yDiff);
1415 		if ((xDiff == 0) || (yDiff == 0)) {
1416 			// Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1417 			const int xEnd = x_ - xDelta;
1418 			const int left = std::min(x, xEnd);
1419 			const int width = std::abs(x - xEnd) + 1;
1420 			const int yEnd = y_ - yDelta;
1421 			const int top = std::min(y, yEnd);
1422 			const int height = std::abs(y - yEnd) + 1;
1423 			const D2D1_RECT_F rectangle1 = D2D1::RectF(static_cast<float>(left), static_cast<float>(top),
1424 				static_cast<float>(left+width), static_cast<float>(top+height));
1425 			pRenderTarget->FillRectangle(&rectangle1, pBrush);
1426 		} else if ((std::abs(xDiff) == std::abs(yDiff))) {
1427 			// 45 degree slope
1428 			pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1429 				D2D1::Point2F(x_ + 0.5f - xDelta, y_ + 0.5f - yDelta), pBrush);
1430 		} else {
1431 			// Line has a different slope so difficult to avoid last pixel
1432 			pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1433 				D2D1::Point2F(x_ + 0.5f, y_ + 0.5f), pBrush);
1434 		}
1435 		x = x_;
1436 		y = y_;
1437 	}
1438 }
1439 
Polygon(Point * pts,size_t npts,ColourDesired fore,ColourDesired back)1440 void SurfaceD2D::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) {
1441 	PLATFORM_ASSERT(pRenderTarget && (npts > 2));
1442 	if (pRenderTarget) {
1443 		ID2D1PathGeometry *geometry = nullptr;
1444 		HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry);
1445 		PLATFORM_ASSERT(geometry);
1446 		if (SUCCEEDED(hr) && geometry) {
1447 			ID2D1GeometrySink *sink = nullptr;
1448 			hr = geometry->Open(&sink);
1449 			PLATFORM_ASSERT(sink);
1450 			if (SUCCEEDED(hr) && sink) {
1451 				sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1452 				for (size_t i=1; i<npts; i++) {
1453 					sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1454 				}
1455 				sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1456 				sink->Close();
1457 				ReleaseUnknown(sink);
1458 
1459 				D2DPenColour(back);
1460 				pRenderTarget->FillGeometry(geometry,pBrush);
1461 				D2DPenColour(fore);
1462 				pRenderTarget->DrawGeometry(geometry,pBrush);
1463 			}
1464 			ReleaseUnknown(geometry);
1465 		}
1466 	}
1467 }
1468 
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)1469 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1470 	if (pRenderTarget) {
1471 		const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top+0.5f, std::round(rc.right) - 0.5f, rc.bottom-0.5f);
1472 		D2DPenColour(back);
1473 		pRenderTarget->FillRectangle(&rectangle1, pBrush);
1474 		D2DPenColour(fore);
1475 		pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1476 	}
1477 }
1478 
FillRectangle(PRectangle rc,ColourDesired back)1479 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1480 	if (pRenderTarget) {
1481 		D2DPenColour(back);
1482 		const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom);
1483 		pRenderTarget->FillRectangle(&rectangle1, pBrush);
1484 	}
1485 }
1486 
FillRectangle(PRectangle rc,Surface & surfacePattern)1487 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1488 	SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern);
1489 	PLATFORM_ASSERT(psurfOther);
1490 	psurfOther->FlushDrawing();
1491 	ID2D1Bitmap *pBitmap = nullptr;
1492 	HRESULT hr = psurfOther->GetBitmap(&pBitmap);
1493 	if (SUCCEEDED(hr) && pBitmap) {
1494 		ID2D1BitmapBrush *pBitmapBrush = nullptr;
1495 		const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1496 	        D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1497 			D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1498 		// Create the bitmap brush.
1499 		hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1500 		ReleaseUnknown(pBitmap);
1501 		if (SUCCEEDED(hr) && pBitmapBrush) {
1502 			pRenderTarget->FillRectangle(
1503 				D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1504 				pBitmapBrush);
1505 			ReleaseUnknown(pBitmapBrush);
1506 		}
1507 	}
1508 }
1509 
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)1510 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1511 	if (pRenderTarget) {
1512 		D2D1_ROUNDED_RECT roundedRectFill = {
1513 			D2D1::RectF(rc.left+1.0f, rc.top+1.0f, rc.right-1.0f, rc.bottom-1.0f),
1514 			4, 4};
1515 		D2DPenColour(back);
1516 		pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1517 
1518 		D2D1_ROUNDED_RECT roundedRect = {
1519 			D2D1::RectF(rc.left + 0.5f, rc.top+0.5f, rc.right - 0.5f, rc.bottom-0.5f),
1520 			4, 4};
1521 		D2DPenColour(fore);
1522 		pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1523 	}
1524 }
1525 
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int)1526 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1527 		ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1528 	if (pRenderTarget) {
1529 		if (cornerSize == 0) {
1530 			// When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1531 			const D2D1_RECT_F rectFill = D2D1::RectF(std::round(rc.left) + 1.0f, rc.top + 1.0f, std::round(rc.right) - 1.0f, rc.bottom - 1.0f);
1532 			D2DPenColour(fill, alphaFill);
1533 			pRenderTarget->FillRectangle(rectFill, pBrush);
1534 
1535 			const D2D1_RECT_F rectOutline = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top + 0.5f, std::round(rc.right) - 0.5f, rc.bottom - 0.5f);
1536 			D2DPenColour(outline, alphaOutline);
1537 			pRenderTarget->DrawRectangle(rectOutline, pBrush);
1538 		} else {
1539 			const float cornerSizeF = static_cast<float>(cornerSize);
1540 			D2D1_ROUNDED_RECT roundedRectFill = {
1541 				D2D1::RectF(std::round(rc.left) + 1.0f, rc.top + 1.0f, std::round(rc.right) - 1.0f, rc.bottom - 1.0f),
1542 				cornerSizeF - 1.0f, cornerSizeF - 1.0f };
1543 			D2DPenColour(fill, alphaFill);
1544 			pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1545 
1546 			D2D1_ROUNDED_RECT roundedRect = {
1547 				D2D1::RectF(std::round(rc.left) + 0.5f, rc.top + 0.5f, std::round(rc.right) - 0.5f, rc.bottom - 0.5f),
1548 				cornerSizeF, cornerSizeF};
1549 			D2DPenColour(outline, alphaOutline);
1550 			pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1551 		}
1552 	}
1553 }
1554 
1555 namespace {
1556 
ColorFromColourAlpha(ColourAlpha colour)1557 D2D_COLOR_F ColorFromColourAlpha(ColourAlpha colour) noexcept {
1558 	D2D_COLOR_F col;
1559 	col.r = colour.GetRedComponent();
1560 	col.g = colour.GetGreenComponent();
1561 	col.b = colour.GetBlueComponent();
1562 	col.a = colour.GetAlphaComponent();
1563 	return col;
1564 }
1565 
1566 }
1567 
GradientRectangle(PRectangle rc,const std::vector<ColourStop> & stops,GradientOptions options)1568 void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
1569 	if (pRenderTarget) {
1570 		D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp;
1571 		lgbp.startPoint = D2D1::Point2F(rc.left, rc.top);
1572 		switch (options) {
1573 		case GradientOptions::leftToRight:
1574 			lgbp.endPoint = D2D1::Point2F(rc.right, rc.top);
1575 			break;
1576 		case GradientOptions::topToBottom:
1577 		default:
1578 			lgbp.endPoint = D2D1::Point2F(rc.left, rc.bottom);
1579 			break;
1580 		}
1581 
1582 		std::vector<D2D1_GRADIENT_STOP> gradientStops;
1583 		for (const ColourStop &stop : stops) {
1584 			gradientStops.push_back({ stop.position, ColorFromColourAlpha(stop.colour) });
1585 		}
1586 		ID2D1GradientStopCollection *pGradientStops = nullptr;
1587 		HRESULT hr = pRenderTarget->CreateGradientStopCollection(
1588 			gradientStops.data(), static_cast<UINT32>(gradientStops.size()), &pGradientStops);
1589 		if (FAILED(hr) || !pGradientStops) {
1590 			return;
1591 		}
1592 		ID2D1LinearGradientBrush *pBrushLinear = nullptr;
1593 		hr = pRenderTarget->CreateLinearGradientBrush(
1594 			lgbp, pGradientStops, &pBrushLinear);
1595 		if (SUCCEEDED(hr) && pBrushLinear) {
1596 			const D2D1_RECT_F rectangle = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom);
1597 			pRenderTarget->FillRectangle(&rectangle, pBrushLinear);
1598 			ReleaseUnknown(pBrushLinear);
1599 		}
1600 		ReleaseUnknown(pGradientStops);
1601 	}
1602 }
1603 
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)1604 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1605 	if (pRenderTarget) {
1606 		if (rc.Width() > width)
1607 			rc.left += std::floor((rc.Width() - width) / 2);
1608 		rc.right = rc.left + width;
1609 		if (rc.Height() > height)
1610 			rc.top += std::floor((rc.Height() - height) / 2);
1611 		rc.bottom = rc.top + height;
1612 
1613 		std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width);
1614 		RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width);
1615 
1616 		ID2D1Bitmap *bitmap = nullptr;
1617 		const D2D1_SIZE_U size = D2D1::SizeU(width, height);
1618 		D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1619 		    D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1620 		const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(),
1621                   width * 4, &props, &bitmap);
1622 		if (SUCCEEDED(hr)) {
1623 			const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
1624 			pRenderTarget->DrawBitmap(bitmap, rcDestination);
1625 			ReleaseUnknown(bitmap);
1626 		}
1627 	}
1628 }
1629 
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)1630 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1631 	if (pRenderTarget) {
1632 		const FLOAT radius = rc.Width() / 2.0f;
1633 		D2D1_ELLIPSE ellipse = {
1634 			D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1635 			radius,radius};
1636 
1637 		PenColour(back);
1638 		pRenderTarget->FillEllipse(ellipse, pBrush);
1639 		PenColour(fore);
1640 		pRenderTarget->DrawEllipse(ellipse, pBrush);
1641 	}
1642 }
1643 
Copy(PRectangle rc,Point from,Surface & surfaceSource)1644 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1645 	SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource);
1646 	surfOther.FlushDrawing();
1647 	ID2D1Bitmap *pBitmap = nullptr;
1648 	HRESULT hr = surfOther.GetBitmap(&pBitmap);
1649 	if (SUCCEEDED(hr) && pBitmap) {
1650 		const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
1651 		D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1652 		pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1653 			D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1654 		hr = pRenderTarget->Flush();
1655 		if (FAILED(hr)) {
1656 			Platform::DebugPrintf("Failed Flush 0x%lx\n", hr);
1657 		}
1658 		ReleaseUnknown(pBitmap);
1659 	}
1660 }
1661 
1662 class BlobInline : public IDWriteInlineObject {
1663 	XYPOSITION width;
1664 
1665 	// IUnknown
1666 	STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
1667 	STDMETHODIMP_(ULONG)AddRef() override;
1668 	STDMETHODIMP_(ULONG)Release() override;
1669 
1670 	// IDWriteInlineObject
1671 	COM_DECLSPEC_NOTHROW STDMETHODIMP Draw(
1672 		void *clientDrawingContext,
1673 		IDWriteTextRenderer *renderer,
1674 		FLOAT originX,
1675 		FLOAT originY,
1676 		BOOL isSideways,
1677 		BOOL isRightToLeft,
1678 		IUnknown *clientDrawingEffect
1679 		) override;
1680 	COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override;
1681 	COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override;
1682 	COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions(
1683 		DWRITE_BREAK_CONDITION *breakConditionBefore,
1684 		DWRITE_BREAK_CONDITION *breakConditionAfter) override;
1685 public:
BlobInline(XYPOSITION width_=0.0f)1686 	BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) {
1687 	}
1688 	// Defaulted so BlobInline objects can be copied.
1689 	BlobInline(const BlobInline &) = default;
1690 	BlobInline(BlobInline &&) = default;
1691 	BlobInline &operator=(const BlobInline &) = default;
1692 	BlobInline &operator=(BlobInline &&) = default;
~BlobInline()1693 	virtual ~BlobInline() {
1694 	}
1695 };
1696 
1697 /// Implement IUnknown
QueryInterface(REFIID riid,PVOID * ppv)1698 STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) {
1699 	if (!ppv)
1700 		return E_POINTER;
1701 	// Never called so not checked.
1702 	*ppv = nullptr;
1703 	if (riid == IID_IUnknown)
1704 		*ppv = static_cast<IUnknown *>(this);
1705 	if (riid == __uuidof(IDWriteInlineObject))
1706 		*ppv = static_cast<IDWriteInlineObject *>(this);
1707 	if (!*ppv)
1708 		return E_NOINTERFACE;
1709 	return S_OK;
1710 }
1711 
STDMETHODIMP_(ULONG)1712 STDMETHODIMP_(ULONG) BlobInline::AddRef() {
1713 	// Lifetime tied to Platform methods so ignore any reference operations.
1714 	return 1;
1715 }
1716 
STDMETHODIMP_(ULONG)1717 STDMETHODIMP_(ULONG) BlobInline::Release() {
1718 	// Lifetime tied to Platform methods so ignore any reference operations.
1719 	return 1;
1720 }
1721 
1722 /// Implement IDWriteInlineObject
Draw(void *,IDWriteTextRenderer *,FLOAT,FLOAT,BOOL,BOOL,IUnknown *)1723 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw(
1724 	void*,
1725 	IDWriteTextRenderer*,
1726 	FLOAT,
1727 	FLOAT,
1728 	BOOL,
1729 	BOOL,
1730 	IUnknown*) {
1731 	// Since not performing drawing, not necessary to implement
1732 	// Could be implemented by calling back into platform-independent code.
1733 	// This would allow more of the drawing to be mediated through DirectWrite.
1734 	return S_OK;
1735 }
1736 
GetMetrics(DWRITE_INLINE_OBJECT_METRICS * metrics)1737 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics(
1738 	DWRITE_INLINE_OBJECT_METRICS *metrics
1739 ) {
1740 	if (!metrics)
1741 		return E_POINTER;
1742 	metrics->width = width;
1743 	metrics->height = 2;
1744 	metrics->baseline = 1;
1745 	metrics->supportsSideways = FALSE;
1746 	return S_OK;
1747 }
1748 
GetOverhangMetrics(DWRITE_OVERHANG_METRICS * overhangs)1749 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics(
1750 	DWRITE_OVERHANG_METRICS *overhangs
1751 ) {
1752 	if (!overhangs)
1753 		return E_POINTER;
1754 	overhangs->left = 0;
1755 	overhangs->top = 0;
1756 	overhangs->right = 0;
1757 	overhangs->bottom = 0;
1758 	return S_OK;
1759 }
1760 
GetBreakConditions(DWRITE_BREAK_CONDITION * breakConditionBefore,DWRITE_BREAK_CONDITION * breakConditionAfter)1761 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions(
1762 	DWRITE_BREAK_CONDITION *breakConditionBefore,
1763 	DWRITE_BREAK_CONDITION *breakConditionAfter
1764 ) {
1765 	if (!breakConditionBefore || !breakConditionAfter)
1766 		return E_POINTER;
1767 	// Since not performing 2D layout, not necessary to implement
1768 	*breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL;
1769 	*breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL;
1770 	return S_OK;
1771 }
1772 
1773 class ScreenLineLayout : public IScreenLineLayout {
1774 	IDWriteTextLayout *textLayout = nullptr;
1775 	std::string text;
1776 	std::wstring buffer;
1777 	std::vector<BlobInline> blobs;
1778 	static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs);
1779 	static std::wstring ReplaceRepresentation(std::string_view text);
1780 	static size_t GetPositionInLayout(std::string_view text, size_t position);
1781 public:
1782 	ScreenLineLayout(const IScreenLine *screenLine);
1783 	// Deleted so ScreenLineLayout objects can not be copied
1784 	ScreenLineLayout(const ScreenLineLayout &) = delete;
1785 	ScreenLineLayout(ScreenLineLayout &&) = delete;
1786 	ScreenLineLayout &operator=(const ScreenLineLayout &) = delete;
1787 	ScreenLineLayout &operator=(ScreenLineLayout &&) = delete;
1788 	~ScreenLineLayout() override;
1789 	size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override;
1790 	XYPOSITION XFromPosition(size_t caretPosition) override;
1791 	std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override;
1792 };
1793 
1794 // Each char can have its own style, so we fill the textLayout with the textFormat of each char
1795 
FillTextLayoutFormats(const IScreenLine * screenLine,IDWriteTextLayout * textLayout,std::vector<BlobInline> & blobs)1796 void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) {
1797 	// Reserve enough entries up front so they are not moved and the pointers handed
1798 	// to textLayout remain valid.
1799 	const ptrdiff_t numRepresentations = screenLine->RepresentationCount();
1800 	std::string_view text = screenLine->Text();
1801 	const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t');
1802 	blobs.reserve(numRepresentations + numTabs);
1803 
1804 	UINT32 layoutPosition = 0;
1805 
1806 	for (size_t bytePosition = 0; bytePosition < screenLine->Length();) {
1807 		const unsigned char uch = screenLine->Text()[bytePosition];
1808 		const unsigned int byteCount = UTF8BytesOfLead[uch];
1809 		const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
1810 		const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits };
1811 
1812 		XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition);
1813 		if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) {
1814 			Point realPt;
1815 			DWRITE_HIT_TEST_METRICS realCaretMetrics {};
1816 			textLayout->HitTestTextPosition(
1817 				layoutPosition,
1818 				false, // trailing if false, else leading edge
1819 				&realPt.x,
1820 				&realPt.y,
1821 				&realCaretMetrics
1822 			);
1823 
1824 			const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x);
1825 			representationWidth = nextTab - realPt.x;
1826 		}
1827 		if (representationWidth > 0.0f) {
1828 			blobs.push_back(BlobInline(representationWidth));
1829 			textLayout->SetInlineObject(&blobs.back(), textRange);
1830 		};
1831 
1832 		FormatAndMetrics *pfm =
1833 			static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(bytePosition)->GetID());
1834 
1835 		const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength();
1836 		std::wstring fontFamilyName(fontFamilyNameSize, 0);
1837 		const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1);
1838 		if (SUCCEEDED(hrFamily)) {
1839 			textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange);
1840 		}
1841 
1842 		textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange);
1843 		textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange);
1844 		textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange);
1845 
1846 		const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength();
1847 		std::wstring localeName(localeNameSize, 0);
1848 		const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1);
1849 		if (SUCCEEDED(hrLocale)) {
1850 			textLayout->SetLocaleName(localeName.c_str(), textRange);
1851 		}
1852 
1853 		textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange);
1854 
1855 		IDWriteFontCollection *fontCollection = nullptr;
1856 		if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) {
1857 			textLayout->SetFontCollection(fontCollection, textRange);
1858 		}
1859 
1860 		bytePosition += byteCount;
1861 		layoutPosition += codeUnits;
1862 	}
1863 
1864 }
1865 
1866 /* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */
1867 
ReplaceRepresentation(std::string_view text)1868 std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) {
1869 	const TextWide wideText(text, true);
1870 	std::wstring ws(wideText.buffer, wideText.tlen);
1871 	std::replace(ws.begin(), ws.end(), L'\t', L'X');
1872 	return ws;
1873 }
1874 
1875 // Finds the position in the wide character version of the text.
1876 
GetPositionInLayout(std::string_view text,size_t position)1877 size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) {
1878 	const std::string_view textUptoPosition = text.substr(0, position);
1879 	return UTF16Length(textUptoPosition);
1880 }
1881 
ScreenLineLayout(const IScreenLine * screenLine)1882 ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) {
1883 	// If the text is empty, then no need to go through this function
1884 	if (!screenLine || !screenLine->Length())
1885 		return;
1886 
1887 	text = screenLine->Text();
1888 
1889 	// Get textFormat
1890 	FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(0)->GetID());
1891 
1892 	if (!pIDWriteFactory || !pfm->pTextFormat) {
1893 		return;
1894 	}
1895 
1896 	// Convert the string to wstring and replace the original control characters with their representative chars.
1897 	buffer = ReplaceRepresentation(screenLine->Text());
1898 
1899 	// Create a text layout
1900 	const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast<UINT32>(buffer.length()),
1901 		pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout);
1902 	if (!SUCCEEDED(hrCreate)) {
1903 		return;
1904 	}
1905 
1906 	// Fill the textLayout chars with their own formats
1907 	FillTextLayoutFormats(screenLine, textLayout, blobs);
1908 }
1909 
~ScreenLineLayout()1910 ScreenLineLayout::~ScreenLineLayout() {
1911 	ReleaseUnknown(textLayout);
1912 }
1913 
1914 // Get the position from the provided x
1915 
PositionFromX(XYPOSITION xDistance,bool charPosition)1916 size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) {
1917 	if (!textLayout) {
1918 		return 0;
1919 	}
1920 
1921 	// Returns the text position corresponding to the mouse x,y.
1922 	// If hitting the trailing side of a cluster, return the
1923 	// leading edge of the following text position.
1924 
1925 	BOOL isTrailingHit = FALSE;
1926 	BOOL isInside = FALSE;
1927 	DWRITE_HIT_TEST_METRICS caretMetrics {};
1928 
1929 	textLayout->HitTestPoint(
1930 		xDistance,
1931 		0.0f,
1932 		&isTrailingHit,
1933 		&isInside,
1934 		&caretMetrics
1935 	);
1936 
1937 	DWRITE_HIT_TEST_METRICS hitTestMetrics {};
1938 	if (isTrailingHit) {
1939 		FLOAT caretX = 0.0f;
1940 		FLOAT caretY = 0.0f;
1941 
1942 		// Uses hit-testing to align the current caret position to a whole cluster,
1943 		// rather than residing in the middle of a base character + diacritic,
1944 		// surrogate pair, or character + UVS.
1945 
1946 		// Align the caret to the nearest whole cluster.
1947 		textLayout->HitTestTextPosition(
1948 			caretMetrics.textPosition,
1949 			false,
1950 			&caretX,
1951 			&caretY,
1952 			&hitTestMetrics
1953 		);
1954 	}
1955 
1956 	size_t pos;
1957 	if (charPosition) {
1958 		pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition;
1959 	} else {
1960 		pos = isTrailingHit ? hitTestMetrics.textPosition + hitTestMetrics.length : caretMetrics.textPosition;
1961 	}
1962 
1963 	// Get the character position in original string
1964 	return UTF8PositionFromUTF16Position(text, pos);
1965 }
1966 
1967 // Finds the point of the caret position
1968 
XFromPosition(size_t caretPosition)1969 XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) {
1970 	if (!textLayout) {
1971 		return 0.0;
1972 	}
1973 	// Convert byte positions to wchar_t positions
1974 	const size_t position = GetPositionInLayout(text, caretPosition);
1975 
1976 	// Translate text character offset to point x,y.
1977 	DWRITE_HIT_TEST_METRICS caretMetrics {};
1978 	Point pt;
1979 
1980 	textLayout->HitTestTextPosition(
1981 		static_cast<UINT32>(position),
1982 		false, // trailing if false, else leading edge
1983 		&pt.x,
1984 		&pt.y,
1985 		&caretMetrics
1986 	);
1987 
1988 	return pt.x;
1989 }
1990 
1991 // Find the selection range rectangles
1992 
FindRangeIntervals(size_t start,size_t end)1993 std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) {
1994 	std::vector<Interval> ret;
1995 
1996 	if (!textLayout || (start == end)) {
1997 		return ret;
1998 	}
1999 
2000 	// Convert byte positions to wchar_t positions
2001 	const size_t startPos = GetPositionInLayout(text, start);
2002 	const size_t endPos = GetPositionInLayout(text, end);
2003 
2004 	// Find selection range length
2005 	const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos);
2006 
2007 	// Determine actual number of hit-test ranges
2008 	UINT32 actualHitTestCount = 0;
2009 
2010 	// First try with 2 elements and if more needed, allocate.
2011 	std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2);
2012 	textLayout->HitTestTextRange(
2013 		static_cast<UINT32>(startPos),
2014 		static_cast<UINT32>(rangeLength),
2015 		0, // x
2016 		0, // y
2017 		hitTestMetrics.data(),
2018 		static_cast<UINT32>(hitTestMetrics.size()),
2019 		&actualHitTestCount
2020 	);
2021 
2022 	if (actualHitTestCount == 0) {
2023 		return ret;
2024 	}
2025 
2026 	if (hitTestMetrics.size() < actualHitTestCount) {
2027 		// Allocate enough room to return all hit-test metrics.
2028 		hitTestMetrics.resize(actualHitTestCount);
2029 		textLayout->HitTestTextRange(
2030 			static_cast<UINT32>(startPos),
2031 			static_cast<UINT32>(rangeLength),
2032 			0, // x
2033 			0, // y
2034 			hitTestMetrics.data(),
2035 			static_cast<UINT32>(hitTestMetrics.size()),
2036 			&actualHitTestCount
2037 		);
2038 	}
2039 
2040 	// Get the selection ranges behind the text.
2041 
2042 	for (size_t i = 0; i < actualHitTestCount; ++i) {
2043 		// Store selection rectangle
2044 		const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i];
2045 		Interval selectionInterval;
2046 
2047 		selectionInterval.left = htm.left;
2048 		selectionInterval.right = htm.left + htm.width;
2049 
2050 		ret.push_back(selectionInterval);
2051 	}
2052 
2053 	return ret;
2054 }
2055 
Layout(const IScreenLine * screenLine)2056 std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) {
2057 	return std::make_unique<ScreenLineLayout>(screenLine);
2058 }
2059 
DrawTextCommon(PRectangle rc,const Font & font_,XYPOSITION ybase,std::string_view text,UINT fuOptions)2060 void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
2061 	SetFont(font_);
2062 
2063 	// Use Unicode calls
2064 	const TextWide tbuf(text, unicodeMode, codePageText);
2065 	if (pRenderTarget && pTextFormat && pBrush) {
2066 		if (fuOptions & ETO_CLIPPED) {
2067 			const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
2068 			pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
2069 		}
2070 
2071 		// Explicitly creating a text layout appears a little faster
2072 		IDWriteTextLayout *pTextLayout = nullptr;
2073 		const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
2074 				rc.Width(), rc.Height(), &pTextLayout);
2075 		if (SUCCEEDED(hr)) {
2076 			D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
2077 			pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions);
2078 			ReleaseUnknown(pTextLayout);
2079 		}
2080 
2081 		if (fuOptions & ETO_CLIPPED) {
2082 			pRenderTarget->PopAxisAlignedClip();
2083 		}
2084 	}
2085 }
2086 
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)2087 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
2088 	ColourDesired fore, ColourDesired back) {
2089 	if (pRenderTarget) {
2090 		FillRectangle(rc, back);
2091 		D2DPenColour(fore);
2092 		DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE);
2093 	}
2094 }
2095 
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)2096 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
2097 	ColourDesired fore, ColourDesired back) {
2098 	if (pRenderTarget) {
2099 		FillRectangle(rc, back);
2100 		D2DPenColour(fore);
2101 		DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
2102 	}
2103 }
2104 
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)2105 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text,
2106 	ColourDesired fore) {
2107 	// Avoid drawing spaces in transparent mode
2108 	for (const char ch : text) {
2109 		if (ch != ' ') {
2110 			if (pRenderTarget) {
2111 				D2DPenColour(fore);
2112 				DrawTextCommon(rc, font_, ybase, text, 0);
2113 			}
2114 			return;
2115 		}
2116 	}
2117 }
2118 
WidthText(Font & font_,std::string_view text)2119 XYPOSITION SurfaceD2D::WidthText(Font &font_, std::string_view text) {
2120 	FLOAT width = 1.0;
2121 	SetFont(font_);
2122 	const TextWide tbuf(text, unicodeMode, codePageText);
2123 	if (pIDWriteFactory && pTextFormat) {
2124 		// Create a layout
2125 		IDWriteTextLayout *pTextLayout = nullptr;
2126 		const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
2127 		if (SUCCEEDED(hr) && pTextLayout) {
2128 			DWRITE_TEXT_METRICS textMetrics;
2129 			if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
2130 				width = textMetrics.widthIncludingTrailingWhitespace;
2131 			ReleaseUnknown(pTextLayout);
2132 		}
2133 	}
2134 	return width;
2135 }
2136 
MeasureWidths(Font & font_,std::string_view text,XYPOSITION * positions)2137 void SurfaceD2D::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) {
2138 	SetFont(font_);
2139 	if (!pIDWriteFactory || !pTextFormat) {
2140 		// SetFont failed or no access to DirectWrite so give up.
2141 		return;
2142 	}
2143 	const TextWide tbuf(text, unicodeMode, codePageText);
2144 	TextPositions poses(tbuf.tlen);
2145 	// Initialize poses for safety.
2146 	std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f);
2147 	// Create a layout
2148 	IDWriteTextLayout *pTextLayout = nullptr;
2149 	const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
2150 	if (!SUCCEEDED(hrCreate) || !pTextLayout) {
2151 		return;
2152 	}
2153 	constexpr int clusters = stackBufferLength;
2154 	DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
2155 	UINT32 count = 0;
2156 	const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count);
2157 	ReleaseUnknown(pTextLayout);
2158 	if (!SUCCEEDED(hrGetCluster)) {
2159 		return;
2160 	}
2161 	// A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font
2162 	FLOAT position = 0.0f;
2163 	int ti=0;
2164 	for (unsigned int ci=0; ci<count; ci++) {
2165 		for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
2166 			poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length;
2167 		}
2168 		position += clusterMetrics[ci].width;
2169 	}
2170 	PLATFORM_ASSERT(ti == tbuf.tlen);
2171 	if (unicodeMode) {
2172 		// Map the widths given for UTF-16 characters back onto the UTF-8 input string
2173 		int ui=0;
2174 		size_t i=0;
2175 		while (ui<tbuf.tlen) {
2176 			const unsigned char uch = text[i];
2177 			const unsigned int byteCount = UTF8BytesOfLead[uch];
2178 			if (byteCount == 4) {	// Non-BMP
2179 				ui++;
2180 			}
2181 			for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui<tbuf.tlen); bytePos++) {
2182 				positions[i++] = poses.buffer[ui];
2183 			}
2184 			ui++;
2185 		}
2186 		XYPOSITION lastPos = 0.0f;
2187 		if (i > 0)
2188 			lastPos = positions[i-1];
2189 		while (i<text.length()) {
2190 			positions[i++] = lastPos;
2191 		}
2192 	} else if (!IsDBCSCodePage(codePageText)) {
2193 
2194 		// One char per position
2195 		PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen));
2196 		for (int kk=0; kk<tbuf.tlen; kk++) {
2197 			positions[kk] = poses.buffer[kk];
2198 		}
2199 
2200 	} else {
2201 
2202 		// May be one or two bytes per position
2203 		int ui = 0;
2204 		for (size_t i=0; i<text.length() && ui<tbuf.tlen;) {
2205 			positions[i] = poses.buffer[ui];
2206 			if (DBCSIsLeadByte(codePageText, text[i])) {
2207 				positions[i+1] = poses.buffer[ui];
2208 				i += 2;
2209 			} else {
2210 				i++;
2211 			}
2212 
2213 			ui++;
2214 		}
2215 	}
2216 }
2217 
Ascent(Font & font_)2218 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
2219 	SetFont(font_);
2220 	return std::ceil(yAscent);
2221 }
2222 
Descent(Font & font_)2223 XYPOSITION SurfaceD2D::Descent(Font &font_) {
2224 	SetFont(font_);
2225 	return std::ceil(yDescent);
2226 }
2227 
InternalLeading(Font & font_)2228 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
2229 	SetFont(font_);
2230 	return std::floor(yInternalLeading);
2231 }
2232 
Height(Font & font_)2233 XYPOSITION SurfaceD2D::Height(Font &font_) {
2234 	return Ascent(font_) + Descent(font_);
2235 }
2236 
AverageCharWidth(Font & font_)2237 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
2238 	FLOAT width = 1.0;
2239 	SetFont(font_);
2240 	if (pIDWriteFactory && pTextFormat) {
2241 		// Create a layout
2242 		IDWriteTextLayout *pTextLayout = nullptr;
2243 		const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
2244 		const size_t lenAllAlpha = wcslen(wszAllAlpha);
2245 		const HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(lenAllAlpha),
2246 			pTextFormat, 1000.0, 1000.0, &pTextLayout);
2247 		if (SUCCEEDED(hr) && pTextLayout) {
2248 			DWRITE_TEXT_METRICS textMetrics;
2249 			if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
2250 				width = textMetrics.width / lenAllAlpha;
2251 			ReleaseUnknown(pTextLayout);
2252 		}
2253 	}
2254 	return width;
2255 }
2256 
SetClip(PRectangle rc)2257 void SurfaceD2D::SetClip(PRectangle rc) {
2258 	if (pRenderTarget) {
2259 		const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
2260 		pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
2261 		clipsActive++;
2262 	}
2263 }
2264 
FlushCachedState()2265 void SurfaceD2D::FlushCachedState() {
2266 }
2267 
SetUnicodeMode(bool unicodeMode_)2268 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
2269 	unicodeMode=unicodeMode_;
2270 }
2271 
SetDBCSMode(int codePage_)2272 void SurfaceD2D::SetDBCSMode(int codePage_) {
2273 	// No action on window as automatically handled by system.
2274 	codePage = codePage_;
2275 }
2276 
SetBidiR2L(bool)2277 void SurfaceD2D::SetBidiR2L(bool) {
2278 }
2279 
2280 #endif
2281 
Allocate(int technology)2282 Surface *Surface::Allocate(int technology) {
2283 #if defined(USE_D2D)
2284 	if (technology == SCWIN_TECH_GDI)
2285 		return new SurfaceGDI;
2286 	else
2287 		return new SurfaceD2D;
2288 #else
2289 	return new SurfaceGDI;
2290 #endif
2291 }
2292 
~Window()2293 Window::~Window() {
2294 }
2295 
Destroy()2296 void Window::Destroy() {
2297 	if (wid)
2298 		::DestroyWindow(HwndFromWindowID(wid));
2299 	wid = nullptr;
2300 }
2301 
GetPosition() const2302 PRectangle Window::GetPosition() const {
2303 	RECT rc;
2304 	::GetWindowRect(HwndFromWindowID(wid), &rc);
2305 	return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
2306 }
2307 
SetPosition(PRectangle rc)2308 void Window::SetPosition(PRectangle rc) {
2309 	::SetWindowPos(HwndFromWindowID(wid),
2310 		0, static_cast<int>(rc.left), static_cast<int>(rc.top),
2311 		static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE);
2312 }
2313 
2314 namespace {
2315 
RectFromMonitor(HMONITOR hMonitor)2316 static RECT RectFromMonitor(HMONITOR hMonitor) noexcept {
2317 	MONITORINFO mi = {};
2318 	mi.cbSize = sizeof(mi);
2319 	if (GetMonitorInfo(hMonitor, &mi)) {
2320 		return mi.rcWork;
2321 	}
2322 	RECT rc = {0, 0, 0, 0};
2323 	if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
2324 		rc.left = 0;
2325 		rc.top = 0;
2326 		rc.right = 0;
2327 		rc.bottom = 0;
2328 	}
2329 	return rc;
2330 }
2331 
2332 }
2333 
SetPositionRelative(PRectangle rc,const Window * relativeTo)2334 void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
2335 	const DWORD style = GetWindowStyle(HwndFromWindowID(wid));
2336 	if (style & WS_POPUP) {
2337 		POINT ptOther = {0, 0};
2338 		::ClientToScreen(HwndFromWindow(*relativeTo), &ptOther);
2339 		rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y));
2340 
2341 		const RECT rcMonitor = RectFromPRectangle(rc);
2342 
2343 		HMONITOR hMonitor = MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST);
2344 		// If hMonitor is NULL, that's just the main screen anyways.
2345 		const RECT rcWork = RectFromMonitor(hMonitor);
2346 
2347 		if (rcWork.left < rcWork.right) {
2348 			// Now clamp our desired rectangle to fit inside the work area
2349 			// This way, the menu will fit wholly on one screen. An improvement even
2350 			// if you don't have a second monitor on the left... Menu's appears half on
2351 			// one screen and half on the other are just U.G.L.Y.!
2352 			if (rc.right > rcWork.right)
2353 				rc.Move(rcWork.right - rc.right, 0);
2354 			if (rc.bottom > rcWork.bottom)
2355 				rc.Move(0, rcWork.bottom - rc.bottom);
2356 			if (rc.left < rcWork.left)
2357 				rc.Move(rcWork.left - rc.left, 0);
2358 			if (rc.top < rcWork.top)
2359 				rc.Move(0, rcWork.top - rc.top);
2360 		}
2361 	}
2362 	SetPosition(rc);
2363 }
2364 
GetClientPosition() const2365 PRectangle Window::GetClientPosition() const {
2366 	RECT rc={0,0,0,0};
2367 	if (wid)
2368 		::GetClientRect(HwndFromWindowID(wid), &rc);
2369 	return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
2370 }
2371 
Show(bool show)2372 void Window::Show(bool show) {
2373 	if (show)
2374 		::ShowWindow(HwndFromWindowID(wid), SW_SHOWNOACTIVATE);
2375 	else
2376 		::ShowWindow(HwndFromWindowID(wid), SW_HIDE);
2377 }
2378 
InvalidateAll()2379 void Window::InvalidateAll() {
2380 	::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE);
2381 }
2382 
InvalidateRectangle(PRectangle rc)2383 void Window::InvalidateRectangle(PRectangle rc) {
2384 	const RECT rcw = RectFromPRectangle(rc);
2385 	::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE);
2386 }
2387 
SetFont(Font & font)2388 void Window::SetFont(Font &font) {
2389 	SetWindowFont(HwndFromWindowID(wid), font.GetID(), 0);
2390 }
2391 
2392 namespace {
2393 
FlipBitmap(HBITMAP bitmap,int width,int height)2394 void FlipBitmap(HBITMAP bitmap, int width, int height) noexcept {
2395 	HDC hdc = ::CreateCompatibleDC({});
2396 	if (hdc) {
2397 		HBITMAP prevBmp = SelectBitmap(hdc, bitmap);
2398 		::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
2399 		SelectBitmap(hdc, prevBmp);
2400 		::DeleteDC(hdc);
2401 	}
2402 }
2403 
2404 }
2405 
LoadReverseArrowCursor(UINT dpi)2406 HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept {
2407 	HCURSOR reverseArrowCursor {};
2408 
2409 	bool created = false;
2410 	HCURSOR cursor = ::LoadCursor({}, IDC_ARROW);
2411 
2412 	if (dpi != uSystemDPI) {
2413 		const int width = SystemMetricsForDpi(SM_CXCURSOR, dpi);
2414 		const int height = SystemMetricsForDpi(SM_CYCURSOR, dpi);
2415 		HCURSOR copy = static_cast<HCURSOR>(::CopyImage(cursor, IMAGE_CURSOR, width, height, LR_COPYFROMRESOURCE | LR_COPYRETURNORG));
2416 		if (copy) {
2417 			created = copy != cursor;
2418 			cursor = copy;
2419 		}
2420 	}
2421 
2422 	ICONINFO info;
2423 	if (::GetIconInfo(cursor, &info)) {
2424 		BITMAP bmp;
2425 		if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
2426 			FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
2427 			if (info.hbmColor)
2428 				FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
2429 			info.xHotspot = bmp.bmWidth - 1 - info.xHotspot;
2430 
2431 			reverseArrowCursor = ::CreateIconIndirect(&info);
2432 		}
2433 
2434 		::DeleteObject(info.hbmMask);
2435 		if (info.hbmColor)
2436 			::DeleteObject(info.hbmColor);
2437 	}
2438 
2439 	if (created) {
2440 		::DestroyCursor(cursor);
2441 	}
2442 	return reverseArrowCursor;
2443 }
2444 
SetCursor(Cursor curs)2445 void Window::SetCursor(Cursor curs) {
2446 	switch (curs) {
2447 	case cursorText:
2448 		::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
2449 		break;
2450 	case cursorUp:
2451 		::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
2452 		break;
2453 	case cursorWait:
2454 		::SetCursor(::LoadCursor(NULL,IDC_WAIT));
2455 		break;
2456 	case cursorHoriz:
2457 		::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
2458 		break;
2459 	case cursorVert:
2460 		::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
2461 		break;
2462 	case cursorHand:
2463 		::SetCursor(::LoadCursor(NULL,IDC_HAND));
2464 		break;
2465 	case cursorReverseArrow:
2466 	case cursorArrow:
2467 	case cursorInvalid:	// Should not occur, but just in case.
2468 		::SetCursor(::LoadCursor(NULL,IDC_ARROW));
2469 		break;
2470 	}
2471 }
2472 
2473 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
2474    coordinates */
GetMonitorRect(Point pt)2475 PRectangle Window::GetMonitorRect(Point pt) {
2476 	const PRectangle rcPosition = GetPosition();
2477 	POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
2478 		static_cast<LONG>(pt.y + rcPosition.top)};
2479 	HMONITOR hMonitor = MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST);
2480 
2481 	const RECT rcWork = RectFromMonitor(hMonitor);
2482 	if (rcWork.left < rcWork.right) {
2483 		PRectangle rcMonitor(
2484 			rcWork.left - rcPosition.left,
2485 			rcWork.top - rcPosition.top,
2486 			rcWork.right - rcPosition.left,
2487 			rcWork.bottom - rcPosition.top);
2488 		return rcMonitor;
2489 	} else {
2490 		return PRectangle();
2491 	}
2492 }
2493 
2494 struct ListItemData {
2495 	const char *text;
2496 	int pixId;
2497 };
2498 
2499 class LineToItem {
2500 	std::vector<char> words;
2501 
2502 	std::vector<ListItemData> data;
2503 
2504 public:
Clear()2505 	void Clear() noexcept {
2506 		words.clear();
2507 		data.clear();
2508 	}
2509 
Get(size_t index) const2510 	ListItemData Get(size_t index) const noexcept {
2511 		if (index < data.size()) {
2512 			return data[index];
2513 		} else {
2514 			ListItemData missing = {"", -1};
2515 			return missing;
2516 		}
2517 	}
Count() const2518 	int Count() const noexcept {
2519 		return static_cast<int>(data.size());
2520 	}
2521 
AllocItem(const char * text,int pixId)2522 	void AllocItem(const char *text, int pixId) {
2523 		ListItemData lid = { text, pixId };
2524 		data.push_back(lid);
2525 	}
2526 
SetWords(const char * s)2527 	char *SetWords(const char *s) {
2528 		words = std::vector<char>(s, s+strlen(s)+1);
2529 		return &words[0];
2530 	}
2531 };
2532 
2533 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2534 
ListBox()2535 ListBox::ListBox() noexcept {
2536 }
2537 
~ListBox()2538 ListBox::~ListBox() {
2539 }
2540 
2541 class ListBoxX : public ListBox {
2542 	int lineHeight;
2543 	FontID fontCopy;
2544 	int technology;
2545 	RGBAImageSet images;
2546 	LineToItem lti;
2547 	HWND lb;
2548 	bool unicodeMode;
2549 	int desiredVisibleRows;
2550 	unsigned int maxItemCharacters;
2551 	unsigned int aveCharWidth;
2552 	Window *parent;
2553 	int ctrlID;
2554 	UINT dpi;
2555 	IListBoxDelegate *delegate;
2556 	const char *widestItem;
2557 	unsigned int maxCharWidth;
2558 	WPARAM resizeHit;
2559 	PRectangle rcPreSize;
2560 	Point dragOffset;
2561 	Point location;	// Caret location at which the list is opened
2562 	int wheelDelta; // mouse wheel residue
2563 
2564 	HWND GetHWND() const noexcept;
2565 	void AppendListItem(const char *text, const char *numword);
2566 	static void AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept;
2567 	int ItemHeight() const;
2568 	int MinClientWidth() const noexcept;
2569 	int TextOffset() const;
2570 	POINT GetClientExtent() const noexcept;
2571 	POINT MinTrackSize() const;
2572 	POINT MaxTrackSize() const;
2573 	void SetRedraw(bool on) noexcept;
2574 	void OnDoubleClick();
2575 	void OnSelChange();
2576 	void ResizeToCursor();
2577 	void StartResize(WPARAM);
2578 	LRESULT NcHitTest(WPARAM, LPARAM) const;
2579 	void CentreItem(int n);
2580 	void Paint(HDC) noexcept;
2581 	static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2582 
2583 	static constexpr Point ItemInset {0, 0};	// Padding around whole item
2584 	static constexpr Point TextInset {2, 0};	// Padding around text
2585 	static constexpr Point ImageInset {1, 0};	// Padding around image
2586 
2587 public:
ListBoxX()2588 	ListBoxX() : lineHeight(10), fontCopy{}, technology(0), lb{}, unicodeMode(false),
2589 		desiredVisibleRows(9), maxItemCharacters(0), aveCharWidth(8),
2590 		parent(nullptr), ctrlID(0), dpi(USER_DEFAULT_SCREEN_DPI),
2591 		delegate(nullptr),
2592 		widestItem(nullptr), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2593 	}
~ListBoxX()2594 	~ListBoxX() override {
2595 		if (fontCopy) {
2596 			::DeleteObject(fontCopy);
2597 			fontCopy = 0;
2598 		}
2599 	}
2600 	void SetFont(Font &font) override;
2601 	void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) override;
2602 	void SetAverageCharWidth(int width) override;
2603 	void SetVisibleRows(int rows) override;
2604 	int GetVisibleRows() const override;
2605 	PRectangle GetDesiredRect() override;
2606 	int CaretFromEdge() override;
2607 	void Clear() override;
2608 	void Append(char *s, int type = -1) override;
2609 	int Length() override;
2610 	void Select(int n) override;
2611 	int GetSelection() override;
2612 	int Find(const char *prefix) override;
2613 	void GetValue(int n, char *value, int len) override;
2614 	void RegisterImage(int type, const char *xpm_data) override;
2615 	void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
2616 	void ClearRegisteredImages() override;
2617 	void SetDelegate(IListBoxDelegate *lbDelegate) override;
2618 	void SetList(const char *list, char separator, char typesep) override;
2619 	void Draw(DRAWITEMSTRUCT *pDrawItem);
2620 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2621 	static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2622 };
2623 
Allocate()2624 ListBox *ListBox::Allocate() {
2625 	ListBoxX *lb = new ListBoxX();
2626 	return lb;
2627 }
2628 
Create(Window & parent_,int ctrlID_,Point location_,int lineHeight_,bool unicodeMode_,int technology_)2629 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2630 	parent = &parent_;
2631 	ctrlID = ctrlID_;
2632 	location = location_;
2633 	lineHeight = lineHeight_;
2634 	unicodeMode = unicodeMode_;
2635 	technology = technology_;
2636 	HWND hwndParent = HwndFromWindow(*parent);
2637 	HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2638 	// Window created as popup so not clipped within parent client area
2639 	wid = ::CreateWindowEx(
2640 		WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2641 		WS_POPUP | WS_THICKFRAME,
2642 		100,100, 150,80, hwndParent,
2643 		NULL,
2644 		hinstanceParent,
2645 		this);
2646 
2647 	dpi = DpiForWindow(hwndParent);
2648 	POINT locationw = POINTFromPoint(location);
2649 	::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2650 	location = PointFromPOINT(locationw);
2651 }
2652 
SetFont(Font & font)2653 void ListBoxX::SetFont(Font &font) {
2654 	if (font.GetID()) {
2655 		if (fontCopy) {
2656 			::DeleteObject(fontCopy);
2657 			fontCopy = 0;
2658 		}
2659 		FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(font.GetID());
2660 		fontCopy = pfm->HFont();
2661 		SetWindowFont(lb, fontCopy, 0);
2662 	}
2663 }
2664 
SetAverageCharWidth(int width)2665 void ListBoxX::SetAverageCharWidth(int width) {
2666 	aveCharWidth = width;
2667 }
2668 
SetVisibleRows(int rows)2669 void ListBoxX::SetVisibleRows(int rows) {
2670 	desiredVisibleRows = rows;
2671 }
2672 
GetVisibleRows() const2673 int ListBoxX::GetVisibleRows() const {
2674 	return desiredVisibleRows;
2675 }
2676 
GetHWND() const2677 HWND ListBoxX::GetHWND() const noexcept {
2678 	return HwndFromWindowID(GetID());
2679 }
2680 
GetDesiredRect()2681 PRectangle ListBoxX::GetDesiredRect() {
2682 	PRectangle rcDesired = GetPosition();
2683 
2684 	int rows = Length();
2685 	if ((rows == 0) || (rows > desiredVisibleRows))
2686 		rows = desiredVisibleRows;
2687 	rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2688 
2689 	int width = MinClientWidth();
2690 	HDC hdc = ::GetDC(lb);
2691 	HFONT oldFont = SelectFont(hdc, fontCopy);
2692 	SIZE textSize = {0, 0};
2693 	int len = 0;
2694 	if (widestItem) {
2695 		len = static_cast<int>(strlen(widestItem));
2696 		if (unicodeMode) {
2697 			const TextWide tbuf(widestItem, unicodeMode);
2698 			::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2699 		} else {
2700 			::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2701 		}
2702 	}
2703 	TEXTMETRIC tm;
2704 	::GetTextMetrics(hdc, &tm);
2705 	maxCharWidth = tm.tmMaxCharWidth;
2706 	SelectFont(hdc, oldFont);
2707 	::ReleaseDC(lb, hdc);
2708 
2709 	const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2710 	if (width < widthDesired)
2711 		width = widthDesired;
2712 
2713 	rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2714 	if (Length() > rows)
2715 		rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi);
2716 
2717 	AdjustWindowRect(&rcDesired, dpi);
2718 	return rcDesired;
2719 }
2720 
TextOffset() const2721 int ListBoxX::TextOffset() const {
2722 	const int pixWidth = images.GetWidth();
2723 	return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2));
2724 }
2725 
CaretFromEdge()2726 int ListBoxX::CaretFromEdge() {
2727 	PRectangle rc;
2728 	AdjustWindowRect(&rc, dpi);
2729 	return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1);
2730 }
2731 
Clear()2732 void ListBoxX::Clear() {
2733 	ListBox_ResetContent(lb);
2734 	maxItemCharacters = 0;
2735 	widestItem = nullptr;
2736 	lti.Clear();
2737 }
2738 
Append(char *,int)2739 void ListBoxX::Append(char *, int) {
2740 	// This method is no longer called in Scintilla
2741 	PLATFORM_ASSERT(false);
2742 }
2743 
Length()2744 int ListBoxX::Length() {
2745 	return lti.Count();
2746 }
2747 
Select(int n)2748 void ListBoxX::Select(int n) {
2749 	// We are going to scroll to centre on the new selection and then select it, so disable
2750 	// redraw to avoid flicker caused by a painting new selection twice in unselected and then
2751 	// selected states
2752 	SetRedraw(false);
2753 	CentreItem(n);
2754 	ListBox_SetCurSel(lb, n);
2755 	OnSelChange();
2756 	SetRedraw(true);
2757 }
2758 
GetSelection()2759 int ListBoxX::GetSelection() {
2760 	return ListBox_GetCurSel(lb);
2761 }
2762 
2763 // This is not actually called at present
Find(const char *)2764 int ListBoxX::Find(const char *) {
2765 	return LB_ERR;
2766 }
2767 
GetValue(int n,char * value,int len)2768 void ListBoxX::GetValue(int n, char *value, int len) {
2769 	const ListItemData item = lti.Get(n);
2770 	strncpy(value, item.text, len);
2771 	value[len-1] = '\0';
2772 }
2773 
RegisterImage(int type,const char * xpm_data)2774 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2775 	XPM xpmImage(xpm_data);
2776 	images.Add(type, new RGBAImage(xpmImage));
2777 }
2778 
RegisterRGBAImage(int type,int width,int height,const unsigned char * pixelsImage)2779 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2780 	images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage));
2781 }
2782 
ClearRegisteredImages()2783 void ListBoxX::ClearRegisteredImages() {
2784 	images.Clear();
2785 }
2786 
Draw(DRAWITEMSTRUCT * pDrawItem)2787 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2788 	if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2789 		RECT rcBox = pDrawItem->rcItem;
2790 		rcBox.left += TextOffset();
2791 		if (pDrawItem->itemState & ODS_SELECTED) {
2792 			RECT rcImage = pDrawItem->rcItem;
2793 			rcImage.right = rcBox.left;
2794 			// The image is not highlighted
2795 			::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2796 			::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2797 			::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2798 			::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2799 		} else {
2800 			::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2801 			::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2802 			::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2803 		}
2804 
2805 		const ListItemData item = lti.Get(pDrawItem->itemID);
2806 		const int pixId = item.pixId;
2807 		const char *text = item.text;
2808 		const int len = static_cast<int>(strlen(text));
2809 
2810 		RECT rcText = rcBox;
2811 		::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y));
2812 
2813 		if (unicodeMode) {
2814 			const TextWide tbuf(text, unicodeMode);
2815 			::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2816 		} else {
2817 			::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2818 		}
2819 
2820 		// Draw the image, if any
2821 		const RGBAImage *pimage = images.Get(pixId);
2822 		if (pimage) {
2823 			std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology));
2824 			if (technology == SCWIN_TECH_GDI) {
2825 				surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2826 				const long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x);
2827 				const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2828 					left + images.GetWidth(), pDrawItem->rcItem.bottom);
2829 				surfaceItem->DrawRGBAImage(rcImage,
2830 					pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2831 				::SetTextAlign(pDrawItem->hDC, TA_TOP);
2832 			} else {
2833 #if defined(USE_D2D)
2834 				const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2835 					D2D1_RENDER_TARGET_TYPE_DEFAULT,
2836 					D2D1::PixelFormat(
2837 						DXGI_FORMAT_B8G8R8A8_UNORM,
2838 						D2D1_ALPHA_MODE_IGNORE),
2839 					0,
2840 					0,
2841 					D2D1_RENDER_TARGET_USAGE_NONE,
2842 					D2D1_FEATURE_LEVEL_DEFAULT
2843 					);
2844 				ID2D1DCRenderTarget *pDCRT = nullptr;
2845 				HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2846 				if (SUCCEEDED(hr) && pDCRT) {
2847 					RECT rcWindow;
2848 					GetClientRect(pDrawItem->hwndItem, &rcWindow);
2849 					hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2850 					if (SUCCEEDED(hr)) {
2851 						surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2852 						pDCRT->BeginDraw();
2853 						const long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x);
2854 						const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2855 							left + images.GetWidth(), pDrawItem->rcItem.bottom);
2856 						surfaceItem->DrawRGBAImage(rcImage,
2857 							pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2858 						pDCRT->EndDraw();
2859 						ReleaseUnknown(pDCRT);
2860 					}
2861 				}
2862 #endif
2863 			}
2864 		}
2865 	}
2866 }
2867 
AppendListItem(const char * text,const char * numword)2868 void ListBoxX::AppendListItem(const char *text, const char *numword) {
2869 	int pixId = -1;
2870 	if (numword) {
2871 		pixId = 0;
2872 		char ch;
2873 		while ((ch = *++numword) != '\0') {
2874 			pixId = 10 * pixId + (ch - '0');
2875 		}
2876 	}
2877 
2878 	lti.AllocItem(text, pixId);
2879 	const unsigned int len = static_cast<unsigned int>(strlen(text));
2880 	if (maxItemCharacters < len) {
2881 		maxItemCharacters = len;
2882 		widestItem = text;
2883 	}
2884 }
2885 
SetDelegate(IListBoxDelegate * lbDelegate)2886 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
2887 	delegate = lbDelegate;
2888 }
2889 
SetList(const char * list,char separator,char typesep)2890 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2891 	// Turn off redraw while populating the list - this has a significant effect, even if
2892 	// the listbox is not visible.
2893 	SetRedraw(false);
2894 	Clear();
2895 	const size_t size = strlen(list);
2896 	char *words = lti.SetWords(list);
2897 	char *startword = words;
2898 	char *numword = nullptr;
2899 	for (size_t i=0; i < size; i++) {
2900 		if (words[i] == separator) {
2901 			words[i] = '\0';
2902 			if (numword)
2903 				*numword = '\0';
2904 			AppendListItem(startword, numword);
2905 			startword = words + i + 1;
2906 			numword = nullptr;
2907 		} else if (words[i] == typesep) {
2908 			numword = words + i;
2909 		}
2910 	}
2911 	if (startword) {
2912 		if (numword)
2913 			*numword = '\0';
2914 		AppendListItem(startword, numword);
2915 	}
2916 
2917 	// Finally populate the listbox itself with the correct number of items
2918 	const int count = lti.Count();
2919 	::SendMessage(lb, LB_INITSTORAGE, count, 0);
2920 	for (intptr_t j=0; j<count; j++) {
2921 		ListBox_AddItemData(lb, j+1);
2922 	}
2923 	SetRedraw(true);
2924 }
2925 
AdjustWindowRect(PRectangle * rc,UINT dpi)2926 void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept {
2927 	RECT rcw = RectFromPRectangle(*rc);
2928 	if (fnAdjustWindowRectExForDpi) {
2929 		fnAdjustWindowRectExForDpi(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE, dpi);
2930 	} else {
2931 		::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2932 	}
2933 	*rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom);
2934 }
2935 
ItemHeight() const2936 int ListBoxX::ItemHeight() const {
2937 	int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2);
2938 	const int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2);
2939 	if (itemHeight < pixHeight) {
2940 		itemHeight = pixHeight;
2941 	}
2942 	return itemHeight;
2943 }
2944 
MinClientWidth() const2945 int ListBoxX::MinClientWidth() const noexcept {
2946 	return 12 * (aveCharWidth+aveCharWidth/3);
2947 }
2948 
MinTrackSize() const2949 POINT ListBoxX::MinTrackSize() const {
2950 	PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight());
2951 	AdjustWindowRect(&rc, dpi);
2952 	POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2953 	return ret;
2954 }
2955 
MaxTrackSize() const2956 POINT ListBoxX::MaxTrackSize() const {
2957 	PRectangle rc = PRectangle::FromInts(0, 0,
2958 		std::max(static_cast<unsigned int>(MinClientWidth()),
2959 		maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 +
2960 		 TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)),
2961 		ItemHeight() * lti.Count());
2962 	AdjustWindowRect(&rc, dpi);
2963 	POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2964 	return ret;
2965 }
2966 
SetRedraw(bool on)2967 void ListBoxX::SetRedraw(bool on) noexcept {
2968 	::SendMessage(lb, WM_SETREDRAW, on, 0);
2969 	if (on)
2970 		::InvalidateRect(lb, nullptr, TRUE);
2971 }
2972 
ResizeToCursor()2973 void ListBoxX::ResizeToCursor() {
2974 	PRectangle rc = GetPosition();
2975 	POINT ptw;
2976 	::GetCursorPos(&ptw);
2977 	const Point pt = PointFromPOINT(ptw) + dragOffset;
2978 
2979 	switch (resizeHit) {
2980 		case HTLEFT:
2981 			rc.left = pt.x;
2982 			break;
2983 		case HTRIGHT:
2984 			rc.right = pt.x;
2985 			break;
2986 		case HTTOP:
2987 			rc.top = pt.y;
2988 			break;
2989 		case HTTOPLEFT:
2990 			rc.top = pt.y;
2991 			rc.left = pt.x;
2992 			break;
2993 		case HTTOPRIGHT:
2994 			rc.top = pt.y;
2995 			rc.right = pt.x;
2996 			break;
2997 		case HTBOTTOM:
2998 			rc.bottom = pt.y;
2999 			break;
3000 		case HTBOTTOMLEFT:
3001 			rc.bottom = pt.y;
3002 			rc.left = pt.x;
3003 			break;
3004 		case HTBOTTOMRIGHT:
3005 			rc.bottom = pt.y;
3006 			rc.right = pt.x;
3007 			break;
3008 	}
3009 
3010 	const POINT ptMin = MinTrackSize();
3011 	const POINT ptMax = MaxTrackSize();
3012 	// We don't allow the left edge to move at present, but just in case
3013 	rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x);
3014 	rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y);
3015 	rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x);
3016 	rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y);
3017 
3018 	SetPosition(rc);
3019 }
3020 
StartResize(WPARAM hitCode)3021 void ListBoxX::StartResize(WPARAM hitCode) {
3022 	rcPreSize = GetPosition();
3023 	POINT cursorPos;
3024 	::GetCursorPos(&cursorPos);
3025 
3026 	switch (hitCode) {
3027 		case HTRIGHT:
3028 		case HTBOTTOM:
3029 		case HTBOTTOMRIGHT:
3030 			dragOffset.x = rcPreSize.right - cursorPos.x;
3031 			dragOffset.y = rcPreSize.bottom - cursorPos.y;
3032 			break;
3033 
3034 		case HTTOPRIGHT:
3035 			dragOffset.x = rcPreSize.right - cursorPos.x;
3036 			dragOffset.y = rcPreSize.top - cursorPos.y;
3037 			break;
3038 
3039 		// Note that the current hit test code prevents the left edge cases ever firing
3040 		// as we don't want the left edge to be movable
3041 		case HTLEFT:
3042 		case HTTOP:
3043 		case HTTOPLEFT:
3044 			dragOffset.x = rcPreSize.left - cursorPos.x;
3045 			dragOffset.y = rcPreSize.top - cursorPos.y;
3046 			break;
3047 		case HTBOTTOMLEFT:
3048 			dragOffset.x = rcPreSize.left - cursorPos.x;
3049 			dragOffset.y = rcPreSize.bottom - cursorPos.y;
3050 			break;
3051 
3052 		default:
3053 			return;
3054 	}
3055 
3056 	::SetCapture(GetHWND());
3057 	resizeHit = hitCode;
3058 }
3059 
NcHitTest(WPARAM wParam,LPARAM lParam) const3060 LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
3061 	const PRectangle rc = GetPosition();
3062 
3063 	LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
3064 	// There is an apparent bug in the DefWindowProc hit test code whereby it will
3065 	// return HTTOPXXX if the window in question is shorter than the default
3066 	// window caption height + frame, even if one is hovering over the bottom edge of
3067 	// the frame, so workaround that here
3068 	if (hit >= HTTOP && hit <= HTTOPRIGHT) {
3069 		const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi);
3070 		const int yPos = GET_Y_LPARAM(lParam);
3071 		if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
3072 			hit += HTBOTTOM - HTTOP;
3073 		}
3074 	}
3075 
3076 	// Never permit resizing that moves the left edge. Allow movement of top or bottom edge
3077 	// depending on whether the list is above or below the caret
3078 	switch (hit) {
3079 		case HTLEFT:
3080 		case HTTOPLEFT:
3081 		case HTBOTTOMLEFT:
3082 			hit = HTERROR;
3083 			break;
3084 
3085 		case HTTOP:
3086 		case HTTOPRIGHT: {
3087 				// Valid only if caret below list
3088 				if (location.y < rc.top)
3089 					hit = HTERROR;
3090 			}
3091 			break;
3092 
3093 		case HTBOTTOM:
3094 		case HTBOTTOMRIGHT: {
3095 				// Valid only if caret above list
3096 				if (rc.bottom <= location.y)
3097 					hit = HTERROR;
3098 			}
3099 			break;
3100 	}
3101 
3102 	return hit;
3103 }
3104 
OnDoubleClick()3105 void ListBoxX::OnDoubleClick() {
3106 	if (delegate) {
3107 		ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
3108 		delegate->ListNotify(&event);
3109 	}
3110 }
3111 
OnSelChange()3112 void ListBoxX::OnSelChange() {
3113 	if (delegate) {
3114 		ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
3115 		delegate->ListNotify(&event);
3116 	}
3117 }
3118 
GetClientExtent() const3119 POINT ListBoxX::GetClientExtent() const noexcept {
3120 	RECT rc;
3121 	::GetWindowRect(HwndFromWindowID(wid), &rc);
3122 	POINT ret { rc.right - rc.left, rc.bottom - rc.top };
3123 	return ret;
3124 }
3125 
CentreItem(int n)3126 void ListBoxX::CentreItem(int n) {
3127 	// If below mid point, scroll up to centre, but with more items below if uneven
3128 	if (n >= 0) {
3129 		const POINT extent = GetClientExtent();
3130 		const int visible = extent.y/ItemHeight();
3131 		if (visible < Length()) {
3132 			const int top = ListBox_GetTopIndex(lb);
3133 			const int half = (visible - 1) / 2;
3134 			if (n > (top + half))
3135 				ListBox_SetTopIndex(lb, n - half);
3136 		}
3137 	}
3138 }
3139 
3140 // Performs a double-buffered paint operation to avoid flicker
Paint(HDC hDC)3141 void ListBoxX::Paint(HDC hDC) noexcept {
3142 	const POINT extent = GetClientExtent();
3143 	HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
3144 	HDC bitmapDC = ::CreateCompatibleDC(hDC);
3145 	HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
3146 	// The list background is mainly erased during painting, but can be a small
3147 	// unpainted area when at the end of a non-integrally sized list with a
3148 	// vertical scroll bar
3149 	const RECT rc = { 0, 0, extent.x, extent.y };
3150 	::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
3151 	// Paint the entire client area and vertical scrollbar
3152 	::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
3153 	::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
3154 	// Select a stock brush to prevent warnings from BoundsChecker
3155 	SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH));
3156 	SelectBitmap(bitmapDC, hBitmapOld);
3157 	::DeleteDC(bitmapDC);
3158 	::DeleteObject(hBitmap);
3159 }
3160 
ControlWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)3161 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3162 	try {
3163 		ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
3164 		switch (iMessage) {
3165 		case WM_ERASEBKGND:
3166 			return TRUE;
3167 
3168 		case WM_PAINT: {
3169 				PAINTSTRUCT ps;
3170 				HDC hDC = ::BeginPaint(hWnd, &ps);
3171 				if (lbx) {
3172 					lbx->Paint(hDC);
3173 				}
3174 				::EndPaint(hWnd, &ps);
3175 			}
3176 			return 0;
3177 
3178 		case WM_MOUSEACTIVATE:
3179 			// This prevents the view activating when the scrollbar is clicked
3180 			return MA_NOACTIVATE;
3181 
3182 		case WM_LBUTTONDOWN: {
3183 				// We must take control of selection to prevent the ListBox activating
3184 				// the popup
3185 				const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
3186 				const int item = LOWORD(lResult);
3187 				if (HIWORD(lResult) == 0 && item >= 0) {
3188 					ListBox_SetCurSel(hWnd, item);
3189 					if (lbx) {
3190 						lbx->OnSelChange();
3191 					}
3192 				}
3193 			}
3194 			return 0;
3195 
3196 		case WM_LBUTTONUP:
3197 			return 0;
3198 
3199 		case WM_LBUTTONDBLCLK: {
3200 				if (lbx) {
3201 					lbx->OnDoubleClick();
3202 				}
3203 			}
3204 			return 0;
3205 
3206 		case WM_MBUTTONDOWN:
3207 			// disable the scroll wheel button click action
3208 			return 0;
3209 		}
3210 
3211 		WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
3212 		if (prevWndProc) {
3213 			return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam);
3214 		} else {
3215 			return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3216 		}
3217 	} catch (...) {
3218 	}
3219 	return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3220 }
3221 
WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)3222 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3223 	switch (iMessage) {
3224 	case WM_CREATE: {
3225 			HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent));
3226 			// Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
3227 			// but has useful side effect of speeding up list population significantly
3228 			lb = ::CreateWindowEx(
3229 				0, TEXT("listbox"), TEXT(""),
3230 				WS_CHILD | WS_VSCROLL | WS_VISIBLE |
3231 				LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
3232 				0, 0, 150,80, hWnd,
3233 				reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)),
3234 				hinstanceParent,
3235 				0);
3236 			WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc);
3237 			::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
3238 		}
3239 		break;
3240 
3241 	case WM_SIZE:
3242 		if (lb) {
3243 			SetRedraw(false);
3244 			::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
3245 			// Ensure the selection remains visible
3246 			CentreItem(GetSelection());
3247 			SetRedraw(true);
3248 		}
3249 		break;
3250 
3251 	case WM_PAINT: {
3252 			PAINTSTRUCT ps;
3253 			::BeginPaint(hWnd, &ps);
3254 			::EndPaint(hWnd, &ps);
3255 		}
3256 		break;
3257 
3258 	case WM_COMMAND:
3259 		// This is not actually needed now - the registered double click action is used
3260 		// directly to action a choice from the list.
3261 		::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam);
3262 		break;
3263 
3264 	case WM_MEASUREITEM: {
3265 			MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
3266 			pMeasureItem->itemHeight = ItemHeight();
3267 		}
3268 		break;
3269 
3270 	case WM_DRAWITEM:
3271 		Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
3272 		break;
3273 
3274 	case WM_DESTROY:
3275 		lb = 0;
3276 		SetWindowPointer(hWnd, nullptr);
3277 		return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3278 
3279 	case WM_ERASEBKGND:
3280 		// To reduce flicker we can elide background erasure since this window is
3281 		// completely covered by its child.
3282 		return TRUE;
3283 
3284 	case WM_GETMINMAXINFO: {
3285 			MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
3286 			minMax->ptMaxTrackSize = MaxTrackSize();
3287 			minMax->ptMinTrackSize = MinTrackSize();
3288 		}
3289 		break;
3290 
3291 	case WM_MOUSEACTIVATE:
3292 		return MA_NOACTIVATE;
3293 
3294 	case WM_NCHITTEST:
3295 		return NcHitTest(wParam, lParam);
3296 
3297 	case WM_NCLBUTTONDOWN:
3298 		// We have to implement our own window resizing because the DefWindowProc
3299 		// implementation insists on activating the resized window
3300 		StartResize(wParam);
3301 		return 0;
3302 
3303 	case WM_MOUSEMOVE: {
3304 			if (resizeHit == 0) {
3305 				return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3306 			} else {
3307 				ResizeToCursor();
3308 			}
3309 		}
3310 		break;
3311 
3312 	case WM_LBUTTONUP:
3313 	case WM_CANCELMODE:
3314 		if (resizeHit != 0) {
3315 			resizeHit = 0;
3316 			::ReleaseCapture();
3317 		}
3318 		return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3319 	case WM_MOUSEWHEEL:
3320 		wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam);
3321 		if (std::abs(wheelDelta) >= WHEEL_DELTA) {
3322 			const int nRows = GetVisibleRows();
3323 			int linesToScroll = 1;
3324 			if (nRows > 1) {
3325 				linesToScroll = nRows - 1;
3326 			}
3327 			if (linesToScroll > 3) {
3328 				linesToScroll = 3;
3329 			}
3330 			linesToScroll *= (wheelDelta / WHEEL_DELTA);
3331 			int top = ListBox_GetTopIndex(lb) + linesToScroll;
3332 			if (top < 0) {
3333 				top = 0;
3334 			}
3335 			ListBox_SetTopIndex(lb, top);
3336 			// update wheel delta residue
3337 			if (wheelDelta >= 0)
3338 				wheelDelta = wheelDelta % WHEEL_DELTA;
3339 			else
3340 				wheelDelta = - (-wheelDelta % WHEEL_DELTA);
3341 		}
3342 		break;
3343 
3344 	default:
3345 		return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3346 	}
3347 
3348 	return 0;
3349 }
3350 
StaticWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)3351 LRESULT PASCAL ListBoxX::StaticWndProc(
3352     HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3353 	if (iMessage == WM_CREATE) {
3354 		CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
3355 		SetWindowPointer(hWnd, pCreate->lpCreateParams);
3356 	}
3357 	// Find C++ object associated with window.
3358 	ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd));
3359 	if (lbx) {
3360 		return lbx->WndProc(hWnd, iMessage, wParam, lParam);
3361 	} else {
3362 		return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3363 	}
3364 }
3365 
3366 namespace {
3367 
ListBoxX_Register()3368 bool ListBoxX_Register() noexcept {
3369 	WNDCLASSEX wndclassc {};
3370 	wndclassc.cbSize = sizeof(wndclassc);
3371 	// We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
3372 	// truncated items in the list and the appearance/disappearance of the vertical scroll bar.
3373 	// The list repaint is double-buffered to avoid the flicker this would otherwise cause.
3374 	wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
3375 	wndclassc.cbWndExtra = sizeof(ListBoxX *);
3376 	wndclassc.hInstance = hinstPlatformRes;
3377 	wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
3378 	wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
3379 	wndclassc.lpszClassName = ListBoxX_ClassName;
3380 
3381 	return ::RegisterClassEx(&wndclassc) != 0;
3382 }
3383 
ListBoxX_Unregister()3384 void ListBoxX_Unregister() noexcept {
3385 	if (hinstPlatformRes) {
3386 		::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes);
3387 	}
3388 }
3389 
3390 }
3391 
Menu()3392 Menu::Menu() noexcept : mid{} {
3393 }
3394 
CreatePopUp()3395 void Menu::CreatePopUp() {
3396 	Destroy();
3397 	mid = ::CreatePopupMenu();
3398 }
3399 
Destroy()3400 void Menu::Destroy() {
3401 	if (mid)
3402 		::DestroyMenu(static_cast<HMENU>(mid));
3403 	mid = 0;
3404 }
3405 
Show(Point pt,Window & w)3406 void Menu::Show(Point pt, Window &w) {
3407 	::TrackPopupMenu(static_cast<HMENU>(mid),
3408 		TPM_RIGHTBUTTON, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0,
3409 		HwndFromWindow(w), nullptr);
3410 	Destroy();
3411 }
3412 
3413 class DynamicLibraryImpl : public DynamicLibrary {
3414 protected:
3415 	HMODULE h;
3416 public:
DynamicLibraryImpl(const char * modulePath)3417 	explicit DynamicLibraryImpl(const char *modulePath) noexcept {
3418 		h = ::LoadLibraryA(modulePath);
3419 	}
3420 
~DynamicLibraryImpl()3421 	~DynamicLibraryImpl() override {
3422 		if (h)
3423 			::FreeLibrary(h);
3424 	}
3425 
3426 	// Use GetProcAddress to get a pointer to the relevant function.
FindFunction(const char * name)3427 	Function FindFunction(const char *name) noexcept override {
3428 		if (h) {
3429 			// Use memcpy as it doesn't invoke undefined or conditionally defined behaviour.
3430 			FARPROC fp = ::GetProcAddress(h, name);
3431 			Function f = nullptr;
3432 			static_assert(sizeof(f) == sizeof(fp));
3433 			memcpy(&f, &fp, sizeof(f));
3434 			return f;
3435 		} else {
3436 			return nullptr;
3437 		}
3438 	}
3439 
IsValid()3440 	bool IsValid() noexcept override {
3441 		return h != NULL;
3442 	}
3443 };
3444 
Load(const char * modulePath)3445 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3446 	return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3447 }
3448 
Chrome()3449 ColourDesired Platform::Chrome() {
3450 	return ColourDesired(::GetSysColor(COLOR_3DFACE));
3451 }
3452 
ChromeHighlight()3453 ColourDesired Platform::ChromeHighlight() {
3454 	return ColourDesired(::GetSysColor(COLOR_3DHIGHLIGHT));
3455 }
3456 
DefaultFont()3457 const char *Platform::DefaultFont() {
3458 	return "Verdana";
3459 }
3460 
DefaultFontSize()3461 int Platform::DefaultFontSize() {
3462 	return 8;
3463 }
3464 
DoubleClickTime()3465 unsigned int Platform::DoubleClickTime() {
3466 	return ::GetDoubleClickTime();
3467 }
3468 
DebugDisplay(const char * s)3469 void Platform::DebugDisplay(const char *s) {
3470 	::OutputDebugStringA(s);
3471 }
3472 
3473 //#define TRACE
3474 
3475 #ifdef TRACE
DebugPrintf(const char * format,...)3476 void Platform::DebugPrintf(const char *format, ...) {
3477 	char buffer[2000];
3478 	va_list pArguments;
3479 	va_start(pArguments, format);
3480 	vsprintf(buffer,format,pArguments);
3481 	va_end(pArguments);
3482 	Platform::DebugDisplay(buffer);
3483 }
3484 #else
DebugPrintf(const char *,...)3485 void Platform::DebugPrintf(const char *, ...) {
3486 }
3487 #endif
3488 
3489 static bool assertionPopUps = true;
3490 
ShowAssertionPopUps(bool assertionPopUps_)3491 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3492 	const bool ret = assertionPopUps;
3493 	assertionPopUps = assertionPopUps_;
3494 	return ret;
3495 }
3496 
Assert(const char * c,const char * file,int line)3497 void Platform::Assert(const char *c, const char *file, int line) {
3498 	char buffer[2000] {};
3499 	sprintf(buffer, "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n");
3500 	if (assertionPopUps) {
3501 		const int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3502 			MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3503 		if (idButton == IDRETRY) {
3504 			::DebugBreak();
3505 		} else if (idButton == IDIGNORE) {
3506 			// all OK
3507 		} else {
3508 			abort();
3509 		}
3510 	} else {
3511 		Platform::DebugDisplay(buffer);
3512 		::DebugBreak();
3513 		abort();
3514 	}
3515 }
3516 
Platform_Initialise(void * hInstance)3517 void Platform_Initialise(void *hInstance) noexcept {
3518 	hinstPlatformRes = static_cast<HINSTANCE>(hInstance);
3519 	LoadDpiForWindow();
3520 	ListBoxX_Register();
3521 }
3522 
Platform_Finalise(bool fromDllMain)3523 void Platform_Finalise(bool fromDllMain) noexcept {
3524 #if defined(USE_D2D)
3525 	if (!fromDllMain) {
3526 		ReleaseUnknown(defaultRenderingParams);
3527 		ReleaseUnknown(customClearTypeRenderingParams);
3528 		ReleaseUnknown(pIDWriteFactory);
3529 		ReleaseUnknown(pD2DFactory);
3530 		if (hDLLDWrite) {
3531 			FreeLibrary(hDLLDWrite);
3532 			hDLLDWrite = {};
3533 		}
3534 		if (hDLLD2D) {
3535 			FreeLibrary(hDLLD2D);
3536 			hDLLD2D = {};
3537 		}
3538 	}
3539 #endif
3540 	if (!fromDllMain && hDLLShcore) {
3541 		FreeLibrary(hDLLShcore);
3542 		hDLLShcore = {};
3543 	}
3544 	ListBoxX_Unregister();
3545 }
3546 
3547 }
3548