Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
GDI.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <algorithm>
7#include <cstdio>
8#include <set>
9
13#include "Stroika/Foundation/Execution/Throw.h"
15
16#include "GDI.h"
17
18using namespace Stroika::Foundation;
20
21using namespace Stroika::Frameworks;
22using namespace Stroika::Frameworks::Led;
23
24#if qStroika_Platform_Windows
25// Often included by <Windows.h> automaticly, but sometimes people define NOIME or VC_EXTRALEAN, and then we
26// must include this manaully.
27#include <windows.h>
28
29#include <imm.h>
30#endif
31
32#if qStroika_Platform_Windows
33// RTL Imaging flags
34#define qUseGetCharPlacementToImage_ 1
35#endif
36
37#if qStroika_Platform_Windows
38/*
39 * Used to use CreateCompatibleBitmap, but as of SPR#1271 try using a DIBSection (of a compatile depth) instead).
40 * This has no noticable effect on normal drawing, but greatly speeds HilightRectangle () code for some computers.
41 */
42#ifndef qUseDIBSectionForOffscreenBitmap_
43#define qUseDIBSectionForOffscreenBitmap_ 1
44#endif
45#endif
46
47#if qStroika_Platform_Windows
48inline bool operator== (PALETTEENTRY lhs, COLORREF rhs)
49{
50 return RGB (lhs.peRed, lhs.peGreen, lhs.peBlue) == rhs;
51}
52#endif
53
54#if !qStroika_Frameworks_Led_HaveWindowsDIBDefined
55#ifndef BI_BITFIELDS
56#define BI_BITFIELDS 3
57#endif
58#endif
59
60#if qStroika_Platform_Windows
61#ifdef _UNICODE
62const bool kRunning32BitGDI = true; // UNICODE only supported on 32GDI (NT or Win2k or Later)
63#else
64const bool kRunning32BitGDI = ((::GetVersion () & 0x80000000) == 0); // I BELIEVE this is how we can test we are under NT!!!
65#endif // Should be a better way to check for 32bit GDI!!!
66#endif
67
68#if qStroika_Platform_Windows
69inline void Win32_GetTextExtentExPoint (HDC hdc, const Led_tChar* str, size_t nChars, int maxExtent, LPINT lpnFit, LPINT alpDx, LPSIZE lpSize)
70{
71 Require (nChars < static_cast<size_t> (numeric_limits<int>::max ()));
72 Verify (::GetTextExtentExPointW (hdc, str, static_cast<int> (nChars), maxExtent, lpnFit, alpDx, lpSize));
73}
74inline void Win32_GetTextExtentPoint (HDC hdc, const Led_tChar* str, int nChars, LPSIZE lpSize)
75{
76 Verify (::GetTextExtentPointW (hdc, str, nChars, lpSize));
77}
78inline void Win32_TextOut (HDC hdc, int xStart, int yStart, const Led_tChar* str, int nChars)
79{
80 Verify (::TextOutW (hdc, xStart, yStart, str, nChars));
81}
82#endif
83
84namespace {
85 inline bool IS_WIN30_DIB (const Led_DIB* dib)
86 {
87 // Logic from MSFT DibLook sample in MSVC.Net 2003
88 RequireNotNull (dib);
89 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
90 return Led_ByteSwapFromWindows (hdr.biSize) == sizeof (BITMAPINFOHEADER);
91 }
92 inline size_t DIBNumColors (const Led_DIB* dib)
93 {
94 // Logic from MSFT DibLook sample in MSVC.Net 2003
95 RequireNotNull (dib);
96 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
97
98 /* If this is a Windows-style DIB, the number of colors in the
99 * color table can be less than the number of bits per pixel
100 * allows for (i.e. lpbi->biClrUsed can be set to some value).
101 * If this is the case, return the appropriate value.
102 */
103 if (IS_WIN30_DIB (dib)) {
104 unsigned long clrUsed = Led_ByteSwapFromWindows (hdr.biClrUsed);
105 if (clrUsed != 0) {
106 return clrUsed;
107 }
108 }
109
110 /* Calculate the number of colors in the color table based on
111 * the number of bits per pixel for the DIB.
112 */
113 unsigned short bitCount = IS_WIN30_DIB (dib) ? Led_ByteSwapFromWindows (hdr.biBitCount)
114 : Led_ByteSwapFromWindows (((const BITMAPCOREHEADER*)dib)->bcBitCount);
115
116 /* return number of colors based on bits per pixel */
117 switch (bitCount) {
118 case 1:
119 return 2;
120 case 4:
121 return 16;
122 case 8:
123 return 256;
124 default:
125 return 0;
126 }
127 }
128}
129
130#if qStroika_Platform_Windows
131namespace {
132
133 inline RGBQUAD mkRGBQuad (COLORREF c)
134 {
135 RGBQUAD r;
136 r.rgbBlue = GetBValue (c);
137 r.rgbGreen = GetGValue (c);
138 r.rgbRed = GetRValue (c);
139 r.rgbReserved = 0;
140 return r;
141 }
142 inline void MaybeAddColorRefToTable_ (RGBQUAD colorTable[256], size_t* iP, COLORREF c)
143 {
144 Assert (sizeof (RGBQUAD) == sizeof (COLORREF));
145 COLORREF* ct = reinterpret_cast<COLORREF*> (colorTable); // use COLORREF instead of RGBQUAD cuz same size but COLOREF has op== defined
146 if (find (ct, ct + *iP, c) == ct + *iP and c != RGB (255, 255, 255)) {
147 colorTable[*iP] = mkRGBQuad (c);
148 ++(*iP);
149 }
150 }
151
152 void CreateStandardColorTable (RGBQUAD colorTable[256], COLORREF c1 = RGB (0, 0, 0), COLORREF c2 = RGB (0, 0, 0),
153 COLORREF c3 = RGB (0, 0, 0), COLORREF c4 = RGB (0, 0, 0))
154 {
155 /*
156 * Cannot use ::GetStockObject (DEFAULT_PALETTE) - because - believe it or not - it returns a 20-entry pallete.
157 */
158 (void)::memset (colorTable, 0, sizeof (RGBQUAD) * 256);
159
160 const BYTE kColorSpecVals[] = {0, 32, 64, 128, 192, 255};
161 const size_t kColorSpecValCnt = sizeof (kColorSpecVals) / sizeof (kColorSpecVals[0]);
162
163 size_t i = 0;
164 {
165 /*
166 * Fill in the color table with all X x X x X entries where X varies over the
167 * kColorSpecVals. This gives us a pretty good coverage of colors.
168 */
169 size_t redIdx = 0;
170 size_t greenIdx = 0;
171 size_t blueIdx = 0;
172 for (; i < 256; ++i) {
173 colorTable[i].rgbRed = kColorSpecVals[redIdx];
174 colorTable[i].rgbGreen = kColorSpecVals[greenIdx];
175 colorTable[i].rgbBlue = kColorSpecVals[blueIdx];
176 ++blueIdx;
177 if (blueIdx >= kColorSpecValCnt) {
178 blueIdx = 0;
179 ++greenIdx;
180 if (greenIdx >= kColorSpecValCnt) {
181 greenIdx = 0;
182 ++redIdx;
183 if (redIdx >= kColorSpecValCnt) {
184 Assert (i == kColorSpecValCnt * kColorSpecValCnt * kColorSpecValCnt - 1);
185 break;
186 }
187 }
188 }
189 }
190 }
191 /*
192 * Above algorithm wrote out WHITE at the end (RGB (255,255,255)). We will OVERWRITE that item with other stuff,
193 * (shades of gray) to save WHITE for the very end.
194 */
195
196 --i; // don't count the WHITE color just added. It will be forced to be last.
197 size_t nColorsLeft = 255 - i;
198 Assert (nColorsLeft == 41);
199
200 // Check each color and see if needs to be added. Do this BEFORE removing white so we don't need to check
201 // that specially
202 MaybeAddColorRefToTable_ (colorTable, &i, c1);
203 MaybeAddColorRefToTable_ (colorTable, &i, c2);
204 MaybeAddColorRefToTable_ (colorTable, &i, c3);
205 MaybeAddColorRefToTable_ (colorTable, &i, c4);
206
207 nColorsLeft = 255 - i;
208 Assert (nColorsLeft > 0);
209
210 size_t aveSpace = 255 / nColorsLeft;
211
212 BYTE startAt = static_cast<BYTE> (aveSpace);
213 while (i < 254 and startAt < 255) {
214 COLORREF c = RGB (startAt, startAt, startAt);
215 Assert (sizeof (RGBQUAD) == sizeof (COLORREF));
216 COLORREF* ct = reinterpret_cast<COLORREF*> (colorTable); // use COLORREF instead of RGBQUAD cuz same size but COLOREF has op== defined
217 if (find (ct, ct + i, c) == ct + i) {
218 colorTable[i] = mkRGBQuad (c);
219 ++i;
220 --nColorsLeft;
221 }
222 startAt += static_cast<BYTE> (aveSpace);
223 }
224
225 Assert (nColorsLeft == 255 - i);
226
227 Assert (nColorsLeft < 5); // I'm pretty sure this has to get us under 5 - or pretty close...
228 Assert (nColorsLeft >= 1); // I'm pretty sure this has to get us under 5 - or pretty close...
229
230 for (; i <= 255; ++i) {
231 colorTable[i].rgbRed = static_cast<BYTE> (i);
232 colorTable[i].rgbGreen = static_cast<BYTE> (i);
233 colorTable[i].rgbBlue = static_cast<BYTE> (i);
234 }
235 }
236
237 HPALETTE CreatePaletteForColorTable (const RGBQUAD colorTable[256])
238 {
239 /*
240 * Cannot use ::GetStockObject (DEFAULT_PALETTE) - because - believe it or not - it returns a 20-entry pallete.
241 */
242 Memory::StackBuffer<char> palBuf{sizeof (LOGPALETTE) + sizeof (PALETTEENTRY) * 256};
243 LPLOGPALETTE lplgPal = reinterpret_cast<LPLOGPALETTE> (static_cast<char*> (palBuf));
244
245 lplgPal->palVersion = 0x300;
246 lplgPal->palNumEntries = 256;
247
248 for (size_t i = 0; i <= 255; ++i) {
249 lplgPal->palPalEntry[i].peRed = colorTable[i].rgbRed;
250 lplgPal->palPalEntry[i].peGreen = colorTable[i].rgbGreen;
251 lplgPal->palPalEntry[i].peBlue = colorTable[i].rgbBlue;
252 lplgPal->palPalEntry[i].peFlags = 0;
253 }
254 return ::CreatePalette (lplgPal);
255 }
256
257 HPALETTE CreateStandardPalette (COLORREF c1, COLORREF c2, COLORREF c3, COLORREF c4)
258 {
259 RGBQUAD colorTable[256];
260 CreateStandardColorTable (colorTable, c1, c2, c3, c4);
261 return CreatePaletteForColorTable (colorTable);
262 }
263
264 HBITMAP Create8BitDIBSection (HDC refDC, DWORD dwX, DWORD dwY, const RGBQUAD* colorTable = nullptr, LPBYTE* ppBits = nullptr)
265 {
266 LPBYTE ignored = nullptr;
267 if (ppBits == nullptr) {
268 ppBits = &ignored;
269 }
270
271 const WORD wBits = 8;
272
273 RGBQUAD colorTableBuf[256];
274 if (colorTable == nullptr) {
275 CreateStandardColorTable (colorTableBuf);
276 colorTable = colorTableBuf;
277 }
278
279 // Create the header big enough to contain color table and bitmasks if needed
280 size_t nInfoSize = sizeof (BITMAPINFOHEADER) + sizeof (RGBQUAD) * (1 << wBits);
281 Memory::StackBuffer<char> bmiBuf{nInfoSize};
282 LPBITMAPINFO pbmi = reinterpret_cast<LPBITMAPINFO> (static_cast<char*> (bmiBuf));
283 (void)::memset (pbmi, 0, nInfoSize);
284 pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
285 pbmi->bmiHeader.biWidth = dwX;
286 pbmi->bmiHeader.biHeight = dwY;
287 pbmi->bmiHeader.biPlanes = 1;
288 pbmi->bmiHeader.biBitCount = wBits;
289 pbmi->bmiHeader.biCompression = BI_RGB; // OVERRIDE below for 16 and 32bpp
290
291 for (int i = 0; i < 256; ++i) {
292 pbmi->bmiColors[i].rgbRed = colorTable[i].rgbRed;
293 pbmi->bmiColors[i].rgbGreen = colorTable[i].rgbGreen;
294 pbmi->bmiColors[i].rgbBlue = colorTable[i].rgbBlue;
295 pbmi->bmiColors[i].rgbReserved = 0;
296 }
297 pbmi->bmiHeader.biClrUsed = 256;
298
299 return ::CreateDIBSection (refDC, pbmi, DIB_RGB_COLORS, (void**)ppBits, nullptr, 0);
300 }
301
302 HBITMAP Create16BitDIBSection (HDC refDC, DWORD dwX, DWORD dwY)
303 {
304 // Create the header big enough to contain color table and bitmasks if needed
305 size_t nInfoSize = sizeof (BITMAPINFOHEADER) + 3 * sizeof (DWORD);
306 Memory::StackBuffer<char> bmiBuf{nInfoSize};
307 LPBITMAPINFO pbmi = reinterpret_cast<LPBITMAPINFO> (static_cast<char*> (bmiBuf));
308 (void)::memset (pbmi, 0, nInfoSize);
309 pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
310 pbmi->bmiHeader.biWidth = dwX;
311 pbmi->bmiHeader.biHeight = dwY;
312 pbmi->bmiHeader.biPlanes = 1;
313 pbmi->bmiHeader.biBitCount = 16;
314 {
315 // if it's 16bpp, fill in the masks and OVERRIDE the compression
316 // these are the default masks - you could change them if needed
317 LPDWORD pMasks = (LPDWORD)(pbmi->bmiColors);
318 pMasks[0] = 0x00007c00;
319 pMasks[1] = 0x000003e0;
320 pMasks[2] = 0x0000001f;
321 pbmi->bmiHeader.biCompression = BI_BITFIELDS;
322 }
323 LPBYTE pBits = 0;
324 return ::CreateDIBSection (refDC, pbmi, DIB_RGB_COLORS, (void**)&pBits, nullptr, 0);
325 }
326
327 HBITMAP Create32BitDIBSection (HDC refDC, DWORD dwX, DWORD dwY)
328 {
329 // Create the header big enough to contain color table and bitmasks if needed
330 size_t nInfoSize = sizeof (BITMAPINFOHEADER) + 3 * sizeof (DWORD);
331 Memory::StackBuffer<char> bmiBuf{nInfoSize};
332 LPBITMAPINFO pbmi = reinterpret_cast<LPBITMAPINFO> (static_cast<char*> (bmiBuf));
333 (void)::memset (pbmi, 0, nInfoSize);
334 pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
335 pbmi->bmiHeader.biWidth = dwX;
336 pbmi->bmiHeader.biHeight = dwY;
337 pbmi->bmiHeader.biPlanes = 1;
338 pbmi->bmiHeader.biBitCount = 32;
339 {
340 // if it's 32bpp, fill in the masks and OVERRIDE the compression
341 // these are the default masks - you could change them if needed
342 LPDWORD pMasks = (LPDWORD)(pbmi->bmiColors);
343 pMasks[0] = 0x00ff0000;
344 pMasks[1] = 0x0000ff00;
345 pMasks[2] = 0x000000ff;
346 pbmi->bmiHeader.biCompression = BI_BITFIELDS;
347 }
348 LPBYTE pBits;
349 return ::CreateDIBSection (refDC, pbmi, DIB_RGB_COLORS, (void**)&pBits, nullptr, 0);
350 }
351}
352#endif
353
354#if qStroika_Platform_Windows
355/*
356 ********************************************************************************
357 ************************************ Bitmap ********************************
358 ********************************************************************************
359 */
360BOOL Bitmap::CreateCompatibleBitmap (HDC hdc, DistanceType nWidth, DistanceType nHeight)
361{
362 Assert (m_hObject == nullptr);
363 m_hObject = ::CreateCompatibleBitmap (hdc, nWidth, nHeight);
364 fImageSize = Led_Size (nHeight, nWidth);
365 return (m_hObject != nullptr); // return value backward compat hack...
366}
367
368BOOL Bitmap::CreateCompatibleDIBSection (HDC hdc, DistanceType nWidth, DistanceType nHeight)
369{
370 RequireNotNull (hdc);
371 Require (m_hObject == nullptr);
372 int useDepth = 16; // default to DIBSection depth - seems to work pretty well in most cases
373
374 fImageSize = Led_Size (nHeight, nWidth);
375
376 int deviceDepth = ::GetDeviceCaps (hdc, BITSPIXEL) * ::GetDeviceCaps (hdc, PLANES);
377 if (deviceDepth > 16) {
378 useDepth = 32;
379 }
380 else if (deviceDepth <= 8) {
381 useDepth = 8;
382 }
383 switch (useDepth) {
384 case 8:
385 m_hObject = Create8BitDIBSection (hdc, nWidth, nHeight);
386 break;
387 case 16:
388 m_hObject = Create16BitDIBSection (hdc, nWidth, nHeight);
389 break;
390 case 32:
391 m_hObject = Create32BitDIBSection (hdc, nWidth, nHeight);
392 break;
393 default:
394 Assert (false); //NotReached
395 }
396 return m_hObject != nullptr; // return value backward compat hack...
397}
398
399void Bitmap::LoadBitmap (HINSTANCE hInstance, LPCTSTR lpBitmapName)
400{
401 Require (m_hObject == nullptr);
402 m_hObject = ::LoadBitmap (hInstance, lpBitmapName);
403 Execution::ThrowIfNull (m_hObject);
404 {
405 BITMAP bm{};
406 Verify (::GetObject (m_hObject, sizeof (BITMAP), (LPVOID)&bm));
407 fImageSize = Led_Size (bm.bmHeight, bm.bmWidth);
408 }
409}
410#endif
411
412/*
413 ********************************************************************************
414 ************************************** Color ***********************************
415 ********************************************************************************
416 */
417/*
418 * Color name values from page 79 of Web Design in a Nutshell, O'Reilly, table 5-2.
419 */
420const Color Color::kBlack = Color (0, 0, 0);
421const Color Color::kWhite = Color (Color::kColorValueMax, Color::kColorValueMax, Color::kColorValueMax);
422const Color Color::kRed = Color (Color::kColorValueMax, 0, 0);
423const Color Color::kGreen = Color (0, Color::kColorValueMax / 2, 0);
424const Color Color::kBlue = Color (0, 0, Color::kColorValueMax);
425const Color Color::kCyan = Color (0, Color::kColorValueMax, Color::kColorValueMax);
426const Color Color::kMagenta = Color (Color::kColorValueMax, 0, Color::kColorValueMax);
427const Color Color::kYellow = Color (Color::kColorValueMax, Color::kColorValueMax, 0);
428const Color Color::kMaroon = Color (Color::kColorValueMax / 2, 0, 0);
429const Color Color::kOlive = Color (Color::kColorValueMax / 2, Color::kColorValueMax / 2, 0);
430const Color Color::kNavyBlue = Color (0, 0, Color::kColorValueMax / 2);
431const Color Color::kPurple = Color (Color::kColorValueMax / 2, 0, Color::kColorValueMax / 2);
432const Color Color::kTeal = Color (0, Color::kColorValueMax / 2, Color::kColorValueMax / 2);
433const Color Color::kGray = Color (Color::kColorValueMax / 2, Color::kColorValueMax / 2, Color::kColorValueMax / 2);
434const Color Color::kSilver = Color (Color::kColorValueMax * 3 / 4, Color::kColorValueMax * 3 / 4, Color::kColorValueMax * 3 / 4);
435const Color Color::kDarkGreen = Color (0, (Color::kColorValueMax / 256) * 100, 0);
436const Color Color::kLimeGreen = Color (0, Color::kColorValueMax, 0);
437const Color Color::kFuchsia = Color::kMagenta; // same according to that table
438const Color Color::kAqua = Color::kCyan; // same according to that table
439
440/*
441 ********************************************************************************
442 ****************************** FontSpecification ***************************
443 ********************************************************************************
444 */
445
446/*
447@METHOD: FontSpecification::SetFontName
448@DESCRIPTION: <p>See also @'FontSpecification::GetFontName'.</p>
449*/
450void FontSpecification::SetFontName (const SDKString& fontName)
451{
452#if qStroika_Platform_Windows
453 Characters::CString::Copy (fFontInfo.lfFaceName, std::size (fFontInfo.lfFaceName), fontName.c_str ());
454 fFontInfo.lfCharSet = DEFAULT_CHARSET;
455#endif
456}
457
458#if qStroika_Platform_Windows
459FontSpecification::FontNameSpecifier::FontNameSpecifier (const Characters::SDKChar* from)
460{
461 Characters::CString::Copy (fName, std::size (fName), from);
462}
463#endif
464
465void FontSpecification::SetFontNameSpecifier (FontNameSpecifier fontNameSpecifier)
466{
467#if qStroika_Platform_Windows
468 Characters::CString::Copy (fFontInfo.lfFaceName, std::size (fFontInfo.lfFaceName), fontNameSpecifier.fName);
469 fFontInfo.lfCharSet = DEFAULT_CHARSET;
470#endif
471}
472
473#if qStroika_Platform_Windows
474struct FontSelectionInfo {
475 FontSelectionInfo (BYTE desiredCharset)
476 : fDesiredCharset (desiredCharset)
477 , fUsesBestCharset (false)
478 , fIsTT (false)
479 , fIsANSI_VAR_Font (false)
480 , fIsGoodBackupCharset (false)
481 , fIsFavoriteForCharset (false)
482 , fIsVariablePitch (false)
483 , fStartsWithAt (false)
484 {
485 memset (&fBestFont, 0, sizeof (fBestFont));
486 }
487 BYTE fDesiredCharset;
488 LOGFONT fBestFont;
489 bool fUsesBestCharset;
490 bool fIsTT;
491 bool fIsANSI_VAR_Font; // a good second choice if Arial not present
492 bool fIsGoodBackupCharset;
493 bool fIsFavoriteForCharset;
494 bool fIsVariablePitch;
495 bool fStartsWithAt;
496 inline int Score () const
497 {
498 int score = 0;
499 if (fUsesBestCharset) {
500 score += 10; // underweight - because (at least with Win2K) - the fCharset field almost always set to zero - so cannot be used reliably
501 }
502 if (fIsTT) {
503 score += 20;
504 }
505 if (fIsANSI_VAR_Font) {
506 score += 4;
507 }
508 if (fIsGoodBackupCharset) {
509 score += 2;
510 }
511 if (fIsFavoriteForCharset) {
512 score += 25;
513 }
514 if (fIsVariablePitch) {
515 //unsure if this is desirable or not - good for US - but not sure about Japan?
516 score += 2;
517 }
518 if (fStartsWithAt) {
519 score -= 25; // we don't really support vertical fonts
520 }
521 return score;
522 }
523};
524static int FAR PASCAL EnumFontCallback (const LOGFONT* lplf, const TEXTMETRIC* /*lpntm*/, DWORD fontType, LPARAM arg)
525{
526 // Score each font choice, and pick the 'best' one. Pick randomly if several are 'best'.
527 RequireNotNull (lplf);
528 FontSelectionInfo& result = *(FontSelectionInfo*)arg;
529
530 static LOGFONT theANSILogFont;
531 if (theANSILogFont.lfFaceName[0] == '\0') {
532 HFONT xxx = HFONT (::GetStockObject (ANSI_VAR_FONT));
533 Verify (::GetObject (xxx, sizeof theANSILogFont, &theANSILogFont));
534 }
535
536 FontSelectionInfo potentialResult = result;
537 memcpy (&potentialResult.fBestFont, lplf, sizeof (LOGFONT));
538
539 if (potentialResult.fDesiredCharset == DEFAULT_CHARSET) {
540 potentialResult.fUsesBestCharset = bool (lplf->lfCharSet == theANSILogFont.lfCharSet);
541 }
542 else {
543 potentialResult.fUsesBestCharset = bool (lplf->lfCharSet == potentialResult.fDesiredCharset);
544 }
545 potentialResult.fIsTT = bool (fontType & TRUETYPE_FONTTYPE);
546 potentialResult.fIsANSI_VAR_Font = ::_tcscmp (lplf->lfFaceName, theANSILogFont.lfFaceName) == 0;
547 potentialResult.fIsGoodBackupCharset = (lplf->lfCharSet == DEFAULT_CHARSET or lplf->lfCharSet == theANSILogFont.lfCharSet);
548 {
549 switch (potentialResult.fDesiredCharset) {
550 case SHIFTJIS_CHARSET: {
551 if (potentialResult.fBestFont.lfFaceName == SDKString{Led_SDK_TCHAROF ("MS P Gothic")} or
552 potentialResult.fBestFont.lfFaceName == SDKString{Led_SDK_TCHAROF ("MS Gothic")} or
553 potentialResult.fBestFont.lfFaceName == SDKString{Led_SDK_TCHAROF ("MS PGothic")}) {
554 potentialResult.fIsFavoriteForCharset = true;
555 }
556 } break;
557 case CHINESEBIG5_CHARSET: {
558 if (potentialResult.fBestFont.lfFaceName == SDKString{Led_SDK_TCHAROF ("MS HEI")} or
559 potentialResult.fBestFont.lfFaceName == SDKString{Led_SDK_TCHAROF ("MingLiU")}) {
560 potentialResult.fIsFavoriteForCharset = true;
561 }
562 } break;
563 }
564 }
565 potentialResult.fIsVariablePitch = lplf->lfPitchAndFamily & VARIABLE_PITCH;
566 potentialResult.fStartsWithAt = lplf->lfFaceName[0] == '@';
567
568 if (potentialResult.Score () > result.Score ()) {
569 result = potentialResult;
570 }
571 return 1;
572}
573#endif
574FontSpecification Led::GetStaticDefaultFont ()
575{
576 // Since this can be called alot, and since its value shouldn't change during the lifetime
577 // of Led running, cache the result (and since on windows - at least - it is expensive to compute)
578 static bool sDefaultFontValid = false;
579 static FontSpecification sDefaultFont;
580 if (not sDefaultFontValid) {
581#if qStroika_Platform_Windows
582 sDefaultFont = GetStaticDefaultFont (DEFAULT_CHARSET);
583#endif
584#if qStroika_Platform_Windows
585 sDefaultFont.SetTextColor (Led_GetTextColor ());
586#endif
587 sDefaultFontValid = true;
588 }
589 return (sDefaultFont);
590}
591
592#if qStroika_Platform_Windows
593FontSpecification Led::GetStaticDefaultFont (BYTE charSet)
594{
595 FontSpecification defaultFont;
596 //nb: import to go through the intermediary font so we don't set into the
597 // LOGFONT lots of fields which are part of the chosen font but are
598 // extraneous, and then mess up later choices to change to face name.
599 //
600 // Eg., if our default font is BOLD, that will result in a big weight# being part of
601 // the logFont. But then if the user picks a different face name, we don't want it
602 // to stay bold. Maybe BOLD is a bad example cuz that DOES show up in our UI (menu of
603 // font attriobutes). But pick one attribute (width? or escarpment) which doesn't show
604 // up in our choice lists, and yet gets caried along even after you change face names.
605 //
606 // See spr# 0426 for more details.
608 FontSelectionInfo selectedFont (charSet);
609 WindowDC screenDC (nullptr);
610#if defined(STRICT)
611 ::EnumFontFamilies (screenDC.m_hDC, nullptr, EnumFontCallback, reinterpret_cast<LPARAM> (&selectedFont));
612#else
613 ::EnumFontFamilies (screenDC.m_hDC, nullptr, reinterpret_cast<FONTENUMPROC> (EnumFontCallback), reinterpret_cast<LPARAM> (&selectedFont));
614#endif
615 fooo.LightSetOSRep (selectedFont.fBestFont);
616
617 // EnumFontFamilies seems to pick very bad sizes. Not sure why. No biggie. This works
618 // pretty well. LGP 970613
619 {
620 static LOGFONT theANSILogFont;
621 if (theANSILogFont.lfFaceName[0] == '\0') {
622 HFONT xxx = HFONT (::GetStockObject (ANSI_VAR_FONT));
623 Verify (::GetObject (xxx, sizeof theANSILogFont, &theANSILogFont));
624 }
625 fooo.SetPointSize (min (max (FontSpecification (theANSILogFont).GetPointSize (), static_cast<unsigned short> (8)),
626 static_cast<unsigned short> (14)));
627 }
628
629 defaultFont.MergeIn (fooo);
630 defaultFont.SetTextColor (Led_GetTextColor ());
631 return (defaultFont);
632}
633#endif
634
635/*
636@METHOD: Intersection
637@DESCRIPTION: <p>Compute the subset of the two @'IncrementalFontSpecification' arguments where both parts
638 are valid and identical.</p>
639*/
640IncrementalFontSpecification Led::Intersection (const IncrementalFontSpecification& lhs, const IncrementalFontSpecification& rhs)
641{
642 IncrementalFontSpecification result = lhs;
643
644 // FontName Info
645 {
646 if (not lhs.GetFontNameSpecifier_Valid () or not rhs.GetFontNameSpecifier_Valid () or
647 lhs.GetFontNameSpecifier () != rhs.GetFontNameSpecifier ()) {
648 result.InvalidateFontNameSpecifier ();
649 }
650 }
651
652 // Style Info
653 {
654 if (not lhs.GetStyle_Bold_Valid () or not rhs.GetStyle_Bold_Valid () or lhs.GetStyle_Bold () != rhs.GetStyle_Bold ()) {
655 result.InvalidateStyle_Bold ();
656 }
657 }
658 {
659 if (not lhs.GetStyle_Italic_Valid () or not rhs.GetStyle_Italic_Valid () or lhs.GetStyle_Italic () != rhs.GetStyle_Italic ()) {
660 result.InvalidateStyle_Italic ();
661 }
662 }
663 {
664 if (not lhs.GetStyle_Underline_Valid () or not rhs.GetStyle_Underline_Valid () or lhs.GetStyle_Underline () != rhs.GetStyle_Underline ()) {
665 result.InvalidateStyle_Underline ();
666 }
667 }
668 {
669 if (not lhs.GetStyle_SubOrSuperScript_Valid () or not rhs.GetStyle_SubOrSuperScript_Valid () or
670 lhs.GetStyle_SubOrSuperScript () != rhs.GetStyle_SubOrSuperScript ()) {
671 result.InvalidateStyle_SubOrSuperScript ();
672 }
673 }
674#if qStroika_Platform_Windows
675 {
676 if (not lhs.GetStyle_Strikeout_Valid () or not rhs.GetStyle_Strikeout_Valid () or lhs.GetStyle_Strikeout () != rhs.GetStyle_Strikeout ()) {
677 result.InvalidateStyle_Strikeout ();
678 }
679 }
680#endif
681
682 // Font Color Info
683 {
684 if (not lhs.GetTextColor_Valid () or not rhs.GetTextColor_Valid () or lhs.GetTextColor () != rhs.GetTextColor ()) {
685 result.InvalidateTextColor ();
686 }
687 }
688
689 // Size Info
690 {
691 // careful - cuz InvalidatePointSizeIncrement and InvalidatePointSize both
692 // invalidate the same thing...
693 // Must check that if ANY specification - its the same on both sides (lhs & rhs)
694 bool needsInval = false;
695 if (lhs.GetPointSizeIncrement_Valid () != rhs.GetPointSizeIncrement_Valid () or
696 (lhs.GetPointSizeIncrement_Valid () and lhs.GetPointSizeIncrement () != rhs.GetPointSizeIncrement ())) {
697 needsInval = true;
698 }
699 if (lhs.GetPointSize_Valid () != rhs.GetPointSize_Valid () or (lhs.GetPointSize_Valid () and lhs.GetPointSize () != rhs.GetPointSize ())) {
700 needsInval = true;
701 }
702 if (needsInval) {
703 result.InvalidatePointSize ();
704 }
705 }
706
707 return result;
708}
709
710#if qStroika_Platform_Windows
711/*
712 ********************************************************************************
713 ******************************** Tablet::RecolorHelper *************************
714 ********************************************************************************
715 */
716class Tablet::RecolorHelper {
717public:
718 RecolorHelper (HDC baseHDC, Led_Size size, Color hilightBackColor, Color hilightForeColor, Color oldBackColor, Color oldForeColor);
719 ~RecolorHelper ();
720
721public:
722 static RecolorHelper* CheckCacheAndReconstructIfNeeded (RecolorHelper* _THIS_, HDC baseHDC, Led_Size size, Color hilightBackColor,
723 Color hilightForeColor, Color oldBackColor, Color oldForeColor);
724
725public:
726 nonvirtual void DoRecolor (const Led_Rect& hilightArea);
727
728private:
729 nonvirtual void DoRecolor_SimpleDSTINVERT (const Led_Rect& hilightArea);
730 nonvirtual void DoRecolor_SimplePATINVERT (const Led_Rect& hilightArea);
731
732protected:
733 nonvirtual void DoRecolor_CopyTo8BitManualMungePixAndBack (const Led_Rect& hilightArea);
734
735private:
736 nonvirtual void MakeMappingTable ();
737
738private:
739 uint8_t fMappingTable[256];
740
741private:
742 nonvirtual uint8_t FindClosestColorInColorTable_ (COLORREF c) const;
743
744private:
745 nonvirtual COLORREF MapColor (COLORREF c) const; // use fHilightBackColor etc to map color
746 nonvirtual COLORREF MapColor (RGBQUAD c) const;
747
748private:
749 HDC fBaseDC;
750 Led_Size fSize;
751 RGBQUAD fColorTable[256];
752 HBITMAP fDibSection;
753 LPBYTE fDibData;
754 size_t fDibDataByteCount;
755 HDC fHMemDC;
756 HBITMAP fOldBitmap;
757 COLORREF fHilightBackColor;
758 COLORREF fHilightForeColor;
759 COLORREF fOldBackColor;
760 COLORREF fOldForeColor;
761};
762COLORREF Tablet::RecolorHelper::MapColor (COLORREF c) const
763{
764 float fIntrp;
765 fIntrp = (float)(255 - GetRValue (c)) / 256.0f;
766 BYTE red = static_cast<BYTE> (GetRValue (fHilightBackColor) + fIntrp * GetRValue (fHilightForeColor) - fIntrp * GetRValue (fHilightBackColor));
767
768 fIntrp = (float)(255 - GetGValue (c)) / 256.0f;
769 BYTE green = static_cast<BYTE> (GetGValue (fHilightBackColor) + fIntrp * GetGValue (fHilightForeColor) - fIntrp * GetGValue (fHilightBackColor));
770
771 fIntrp = (float)(255 - GetBValue (c)) / 256.0f;
772 BYTE blue = static_cast<BYTE> (GetBValue (fHilightBackColor) + fIntrp * GetBValue (fHilightForeColor) - fIntrp * GetBValue (fHilightBackColor));
773 return RGB (red, green, blue);
774}
775inline COLORREF Tablet::RecolorHelper::MapColor (RGBQUAD c) const
776{
777 return MapColor (RGB (c.rgbRed, c.rgbGreen, c.rgbBlue));
778}
779
780Tablet::RecolorHelper::RecolorHelper (HDC baseHDC, Led_Size size, Color hilightBackColor, Color hilightForeColor, Color oldBackColor, Color oldForeColor)
781 : fDibData (nullptr)
782 //fMappingTable ()
783 , fDibDataByteCount (0)
784 , fHMemDC (nullptr)
785 , fBaseDC (baseHDC)
786 , fSize (size)
787 , fColorTable ()
788 , fDibSection (nullptr)
789 , fOldBitmap (nullptr)
790 , fHilightBackColor (hilightBackColor.GetOSRep ())
791 , fHilightForeColor (hilightForeColor.GetOSRep ())
792 , fOldBackColor (oldBackColor.GetOSRep ())
793 , fOldForeColor (oldForeColor.GetOSRep ())
794{
795 CreateStandardColorTable (fColorTable, fHilightBackColor, fHilightForeColor, fOldBackColor, fOldForeColor);
796 fDibSection = Create8BitDIBSection (baseHDC, size.h, size.v, fColorTable, &fDibData);
797 BITMAP bm;
798 Verify (::GetObject (fDibSection, sizeof (BITMAP), &bm) == sizeof (BITMAP));
799 fDibDataByteCount = bm.bmWidthBytes * bm.bmHeight;
800 fHMemDC = ::CreateCompatibleDC (fBaseDC);
801 fOldBitmap = reinterpret_cast<HBITMAP> (::SelectObject (fHMemDC, fDibSection));
802 MakeMappingTable ();
803}
804
805Tablet::RecolorHelper::~RecolorHelper ()
806{
807 if (fDibSection != nullptr) {
808 ::SelectObject (fHMemDC, fOldBitmap);
809 ::DeleteDC (fHMemDC);
810 ::DeleteObject (fDibSection);
811 }
812}
813
814Tablet::RecolorHelper* Tablet::RecolorHelper::CheckCacheAndReconstructIfNeeded (RecolorHelper* _THIS_, HDC baseHDC, Led_Size size, Color hilightBackColor,
815 Color hilightForeColor, Color oldBackColor, Color oldForeColor)
816{
817 if (_THIS_ == nullptr or size.h > _THIS_->fSize.h or size.v > _THIS_->fSize.v or baseHDC != _THIS_->fBaseDC or
818 hilightBackColor.GetOSRep () != _THIS_->fHilightBackColor or hilightForeColor.GetOSRep () != _THIS_->fHilightForeColor or
819 oldBackColor.GetOSRep () != _THIS_->fOldBackColor or oldForeColor.GetOSRep () != _THIS_->fOldForeColor) {
820 Led_Size areaSize = size;
821 if (_THIS_ != nullptr) {
822 areaSize.v = max (size.v, _THIS_->fSize.v);
823 areaSize.h = max (size.h, _THIS_->fSize.h);
824 }
825 RecolorHelper* tmp = new RecolorHelper (baseHDC, areaSize, hilightBackColor, hilightForeColor, oldBackColor, oldForeColor);
826 delete _THIS_;
827 return tmp;
828 }
829 return _THIS_;
830}
831
832void Tablet::RecolorHelper::MakeMappingTable ()
833{
834 for (size_t i = 0; i < 256; ++i) {
835 fMappingTable[i] = FindClosestColorInColorTable_ (MapColor (fColorTable[i]));
836 }
837}
838
839uint8_t Tablet::RecolorHelper::FindClosestColorInColorTable_ (COLORREF c) const
840{
841 // walk through the color table and see which color is closest to 'c'
842 uint8_t closest = 0;
843 unsigned int closestDistance = 0xffffffff; // big distance
844 for (size_t i = 0; i < 256; ++i) {
845 unsigned int thisDist = Distance_Squared (c, RGB (fColorTable[i].rgbRed, fColorTable[i].rgbGreen, fColorTable[i].rgbBlue));
846 if (thisDist < closestDistance) {
847 closest = static_cast<uint8_t> (i);
848 closestDistance = thisDist;
849 if (closestDistance == 0) {
850 break; // not needed, but COULD be a slight speed tweek
851 }
852 }
853 }
854 return closest;
855}
856
857void Tablet::RecolorHelper::DoRecolor (const Led_Rect& hilightArea)
858{
859 HPALETTE hPal = nullptr;
860 HPALETTE hOldPal = nullptr;
861 bool isPaletteDevice = !!(::GetDeviceCaps (fBaseDC, RASTERCAPS) & RC_PALETTE);
862 if (isPaletteDevice) {
863 // if it's a palette device, select and realize a palette
864 // as a background palette (won't cause a problem is the
865 // palette was not selected in the foreground in the main app
866 hPal = CreatePaletteForColorTable (fColorTable);
867 Execution::ThrowIfNull (hPal);
868 hOldPal = ::SelectPalette (fBaseDC, hPal, TRUE);
869 ::RealizePalette (fBaseDC);
870 }
871
872 DoRecolor_CopyTo8BitManualMungePixAndBack (hilightArea);
873
874 if (isPaletteDevice) {
875 if (hOldPal != nullptr) {
876 ::SelectPalette (fBaseDC, hOldPal, TRUE);
877 }
878 ::DeleteObject (hPal);
879 }
880}
881
882void Tablet::RecolorHelper::DoRecolor_SimpleDSTINVERT (const Led_Rect& hilightArea)
883{
884 // Does proper inverse video, but seems to ignore the TextColor/BkColor/Pen/Brush colors.
885 // Really should fix this to behave like Mac - replacing the background color with the text hilight color.
886 // See SPR#1271
887 ::BitBlt (fBaseDC, hilightArea.left, hilightArea.top, hilightArea.GetWidth (), hilightArea.GetHeight (), fBaseDC, hilightArea.left,
888 hilightArea.top, DSTINVERT);
889}
890
891void Tablet::RecolorHelper::DoRecolor_SimplePATINVERT (const Led_Rect& hilightArea)
892{
893 // Attempt at solving SPR#1271. Works decently - producing the right background - but the text is colored YELLOW instead of WHITE - and so
894 // doesn't look very good (not enough contrast).
895 Color useColor = Color::kWhite - Color (fHilightBackColor);
896 HGDIOBJ oldPen = ::SelectObject (fBaseDC, ::GetStockObject (NULL_PEN));
897 Brush backgroundBrush (useColor.GetOSRep ());
898 HGDIOBJ oldBrush = ::SelectObject (fBaseDC, backgroundBrush);
899 ::BitBlt (fBaseDC, hilightArea.left, hilightArea.top, hilightArea.GetWidth (), hilightArea.GetHeight (), fBaseDC, hilightArea.left,
900 hilightArea.top, PATINVERT);
901 (void)::SelectObject (fBaseDC, oldPen);
902 (void)::SelectObject (fBaseDC, oldBrush);
903}
904
905void Tablet::RecolorHelper::DoRecolor_CopyTo8BitManualMungePixAndBack (const Led_Rect& hilightArea)
906{
907 // By commenting stuff in and out - I determined that virtuall ALL the time is spent in this first
908 // BitBlt () - LGP 2003-03-11
909 // I also found that qUseDIBSectionForOffscreenBitmap_ made this BitBlt go much faster - to the point of acceptable speed
910 // LGP - 2003-03-12
911
912 // Copy the REAL image into our 8-bit DIBSECTION
913 ::BitBlt (fHMemDC, 0, 0, hilightArea.GetWidth (), hilightArea.GetHeight (), fBaseDC, hilightArea.left, hilightArea.top, SRCCOPY);
914
915 /*
916 *
917 * Fiddle the bits:
918 *
919 * NB: This code assumes we're pointing at an 8-bit COLOR-LOOKUP-TABLE based Image
920 *
921 * Note - this also may be modifying MUCH MORE than is actually needed. It goes all the way to the
922 * end of the ROW of pixels - but we could be using much less.
923 *
924 * In order to remedy that - I tried constructing the DIBSECTION on the fly - instead of caching it. That turned
925 * out to be quite slow (on DELL Precision Workstation 420 (800mz Dual Processor)). This muning code has never shown
926 * up as taking significant time. Its all that BitBlt above.
927 *
928 * Anyhow - if this does someday look slow - it can easily be fixed. We can just break the loop into two nested loops,
929 * the outer one over rows, and the inner row loop stopping NOT at the end of the REAL row - but just at the end
930 * of the subset we are using (easy cuz we always start at 0,0).
931 */
932 Verify (::GdiFlush ()); // make sure bits in sync... - not SURE if this is needed?
933 {
934 const unsigned char* kMappingTable = fMappingTable;
935 unsigned char* dataStart = fDibData;
936 unsigned char* dataEnd = dataStart + fDibDataByteCount;
937 for (unsigned char* i = dataStart; i < dataEnd; ++i) {
938 *i = kMappingTable[*i];
939 }
940 }
941
942 // Copy them back
943 ::BitBlt (fBaseDC, hilightArea.left, hilightArea.top, hilightArea.GetWidth (), hilightArea.GetHeight (), fHMemDC, 0, 0, SRCCOPY);
944}
945#endif
946
947#if qStroika_Frameworks_Led_SupportGDI
948/*
949 ********************************************************************************
950 *********************************** Tablet *************************************
951 ********************************************************************************
952 */
953#if qStroika_Platform_Windows
954Tablet::Tablet (HDC hdc, Tablet::OwnDCControl ownsDC)
955 : m_hDC (hdc)
956 , fRecolorHelper (nullptr)
957 , m_hAttribDC (hdc)
958 , m_bPrinting (false)
959 , fOwnsDC (ownsDC)
960 , fLogPixelsV (0)
961 , fLogPixelsH (0)
962{
963}
964#endif
965
966Tablet::~Tablet ()
967{
968#if qStroika_Platform_Windows
969 delete fRecolorHelper;
970 if (m_hDC != nullptr and fOwnsDC == eOwnsDC) {
971 ::DeleteDC (Detach ());
972 }
973#endif
974}
975
976/*
977@METHOD: Tablet::CvtFromTWIPS
978@DESCRIPTION: <p>Utility routine to convert from TWIPS to logical coordinates (usually pixels).</p>
979 <p>See also @'Tablet::CvtFromTWIPSV', @'Tablet::CvtFromTWIPSH', @'Tablet::CvtToTWIPSV', @'Tablet::CvtToTWIPSH'.</p>
980*/
981Led_Point Tablet::CvtFromTWIPS (TWIPS_Point from) const
982{
983 return Led_Point{CvtFromTWIPSV (from.v), CvtFromTWIPSH (from.h)};
984}
985
986/*
987@METHOD: Tablet::CvtToTWIPS
988@DESCRIPTION: <p>Utility routine to convert from logical coordinates (usually pixels) to TWIPS.</p>
989 <p>See also @'Tablet::CvtFromTWIPSV', @'Tablet::CvtFromTWIPSH', @'Tablet::CvtToTWIPSV', @'Tablet::CvtToTWIPSH'.</p>
990*/
991TWIPS_Point Tablet::CvtToTWIPS (Led_Point from) const
992{
993 return TWIPS_Point{CvtToTWIPSV (from.v), CvtToTWIPSH (from.h)};
994}
995
996/*
997@METHOD: Tablet::CvtFromTWIPS
998@DESCRIPTION: <p>Utility routine to convert from TWIPS to logical coordinates (usually pixels).</p>
999 <p>See also @'Tablet::CvtFromTWIPSV', @'Tablet::CvtFromTWIPSH', @'Tablet::CvtToTWIPSV', @'Tablet::CvtToTWIPSH'.</p>
1000*/
1001Led_Rect Tablet::CvtFromTWIPS (TWIPS_Rect from) const
1002{
1003 return Led_Rect{CvtFromTWIPS (from.GetOrigin ()), Led_Size (CvtFromTWIPS (from.GetSize ()))};
1004}
1005
1006/*
1007@METHOD: Tablet::CvtToTWIPS
1008@DESCRIPTION: <p>Utility routine to convert from logical coordinates (usually pixels) to TWIPS.</p>
1009 <p>See also @'Tablet::CvtFromTWIPSV', @'Tablet::CvtFromTWIPSH', @'Tablet::CvtToTWIPSV', @'Tablet::CvtToTWIPSH'.</p>
1010*/
1011TWIPS_Rect Tablet::CvtToTWIPS (Led_Rect from) const
1012{
1013 return TWIPS_Rect{CvtToTWIPS (from.GetOrigin ()), CvtToTWIPS (Led_Point (from.GetSize ()))};
1014}
1015
1016/*
1017@METHOD: Tablet::ScrollBitsAndInvalRevealed
1018@DESCRIPTION: <p>Scroll the given 'windowRect' by 'scrollVBy localical units. The area of the window exposed by this
1019 action is invalidated (so a later update event will fix it).</p>
1020*/
1021void Tablet::ScrollBitsAndInvalRevealed (const Led_Rect& windowRect, CoordinateType scrollVBy)
1022{
1023#if qStroika_Platform_Windows
1024 RECT gdiMoveRect = AsRECT (windowRect);
1025 // NB: I used to use ScrollDC (Led 2.1 and earlier). But that code appeared to sometimes leave
1026 // little bits of crufy around. I never understood why. But I assume it was a windows bug.
1027 HWND w = GetWindow ();
1028 Execution::ThrowIfNull (w);
1029 ::ScrollWindow (w, 0, scrollVBy, &gdiMoveRect, &gdiMoveRect);
1030#else
1031 Assert (false); //NYI
1032#endif
1033}
1034
1035/*
1036@METHOD: Tablet::FrameRegion
1037@DESCRIPTION: <p>Draw the outline of the given region 'r' in color 'c'.</p>
1038*/
1039void Tablet::FrameRegion (const Region& r, const Color& c)
1040{
1041#if qStroika_Platform_Windows
1042 Brush brush = Brush (c.GetOSRep ());
1043 (void)::FrameRgn (*this, r, brush, 1, 1);
1044#else
1045 Assert (false); // NYI
1046#endif
1047}
1048
1049/*
1050@METHOD: Tablet::FrameRectangle
1051@DESCRIPTION: <p>Draw the outline of the given rectangle 'r' in color 'c' and with
1052 borderWidth (pen width) 'borderWidth'. This function does NOT use the currently selected
1053 pen or brush, or anything like that. It draws a border just INSIDE the rectangle specified
1054 by 'r'.
1055 </p>
1056*/
1057void Tablet::FrameRectangle (const Led_Rect& r, Color c, DistanceType borderWidth)
1058{
1059 /*
1060 * Almost certainly can implement much more efficiently, but leave like this for now to assure pixel-for-pixel
1061 * equiv across GDIs.
1062 */
1063 Led_Rect topBar = Led_Rect (r.top, r.left, borderWidth, r.GetWidth ());
1064 Led_Rect leftBar = Led_Rect (r.top, r.left, r.GetHeight (), borderWidth);
1065 EraseBackground_SolidHelper (topBar, c); // TOP
1066 EraseBackground_SolidHelper (leftBar, c); // LEFT
1067 EraseBackground_SolidHelper (topBar + Led_Point (r.GetHeight () - borderWidth, 0), c); // BOTTOM
1068 EraseBackground_SolidHelper (leftBar + Led_Point (0, r.GetWidth () - borderWidth), c); // RIGHT
1069}
1070
1071/*
1072@METHOD: Tablet::MeasureText
1073@DESCRIPTION: <p>Measure the widths of the given argument @'Led_tChar's. Assign those widths into
1074 the array 'charLocations'. (EXPLAIN CAREFULLY REQUIREMENTS ABOUT BUFSIZE of charLocations and handling of
1075 zero case and offset rules, handling of tabs, etc)</p>
1076 <p>Note that the resulting measured text must come out in non-descreasing order (there can be zero
1077 character widths, but never negative).</p>
1078*/
1079void Tablet::MeasureText (const FontMetrics& precomputedFontMetrics, const Led_tChar* text, size_t nTChars, DistanceType* charLocations)
1080{
1081 RequireNotNull (text);
1082 RequireNotNull (charLocations);
1083
1084#if qStroika_Platform_Windows
1085 DistanceType kMaxTextWidthResult = kRunning32BitGDI ? 0x7fffffff : 0x7fff;
1086 if (IsPrinting ()) {
1087 // See SPR#0435
1088 SIZE ve = GetViewportExt ();
1089 SIZE we = GetWindowExt ();
1090 kMaxTextWidthResult = ::MulDiv (kMaxTextWidthResult, we.cx, ve.cx) - 1;
1091 }
1092#endif
1093 size_t kMaxChars = kMaxTextWidthResult / precomputedFontMetrics.GetMaxCharacterWidth ();
1094 Assert (kMaxChars > 1);
1095
1096 DistanceType runningCharCount = 0;
1097 for (size_t charsToGo = nTChars; charsToGo > 0;) {
1098 size_t i = nTChars - charsToGo;
1099 Assert (i < nTChars);
1100
1101 while (text[i] == '\t') {
1102 Assert (charsToGo > 0);
1103
1104 charLocations[i] = runningCharCount;
1105 ++i;
1106 if (--charsToGo == 0) {
1107 break;
1108 }
1109 }
1110 if (charsToGo == 0) {
1111 break;
1112 }
1113
1114 size_t charsThisTime = min (charsToGo, kMaxChars);
1115 for (size_t tabIndex = 1; tabIndex < charsThisTime; tabIndex++) {
1116 if (text[i + tabIndex] == '\t') {
1117 charLocations[i + tabIndex] = runningCharCount;
1118 charsThisTime = tabIndex;
1119 break;
1120 }
1121 }
1122
1123#if qStroika_Platform_Windows
1124 SIZE size;
1125 Assert (sizeof (int) == sizeof (DistanceType));
1126
1127 Win32_GetTextExtentExPoint (m_hAttribDC, &text[i], charsThisTime, kMaxTextWidthResult, nullptr, (int*)&charLocations[i], &size);
1128 for (size_t j = 0; j < charsThisTime; ++j) {
1129 charLocations[i + j] += runningCharCount;
1130 }
1131#endif
1132
1133 runningCharCount = charLocations[i + charsThisTime - 1];
1134
1135 Assert (charsToGo >= charsThisTime);
1136 charsToGo -= charsThisTime;
1137 }
1138
1139// LGP-991220 - This is generating asserts elsewhere - and seems like such a hack. Not sure why needed. Try getting rid of and see what happens?
1140#if qStroika_Platform_Windows && 0
1141 // This gross hack is cuz we do a GetTextExtent() at the end of the
1142 // DrawText for PC, to see how much we drew. This is the only hack
1143 // I could think of to assure we get consistent results (which is very important).
1144 // This REALLY shold be done better - fix may not be here, but in DrawCode below...
1145 // LGP 960509
1146
1147 if (nTChars > 0) {
1148 int lastWidth = (nTChars == 1) ? charLocations[0] : (charLocations[nTChars - 1] - charLocations[nTChars - 2]);
1149 SIZE size;
1150 AssertNotNull (m_hAttribDC);
1151 Win32_GetTextExtentPoint (m_hAttribDC, &text[nTChars - 1], 1, &size);
1152 int sbWidth = size.cx;
1153 if (sbWidth != lastWidth) {
1154 charLocations[nTChars - 1] = ((nTChars == 1) ? 0 : charLocations[nTChars - 2]) + sbWidth;
1155 }
1156 }
1157#endif
1158
1160 // Assure charLocations are in non-decreasing order (OK to have some zero width - but never negative).
1161 DistanceType d = 0;
1162 for (size_t i = 0; i < nTChars; ++i) {
1163 Ensure (d <= charLocations[i]);
1164 d = charLocations[i];
1165 }
1166 }
1167}
1168
1169/*
1170@DESCRIPTION: <p>Draw the given text (@'Led_tChars') to this tablet object, at the given logical coordinates. Use the given
1171 tabstop origin, and tabStopList object to compute where tabs go. Return the amountDrawn (number of pixels used by draw).
1172 (EXPLAIN CAREFULLY REQUIREMENTS ABOUT BUFSIZE of charLocations and handling of
1173 zero case and offset rules, handling of tabs, AND MUCH MORE etc)</p>
1174 <p>Note - for BIDI text - the VIRTUAL (display) text is what should be passed in. Text will be imaged
1175 left to right. This means that for RTL text such as Arabic or Hebrew - it should have already been
1176 re-ordered in the argument passed in, and any mirroring should have already been done. This routine WILL
1177 take care of any contextual shaping required (glyph selection based on context - as with Arabic).</p>
1178*/
1179void Tablet::TabbedTextOut ([[maybe_unused]] const FontMetrics& precomputedFontMetrics, const Led_tChar* text, size_t nBytes,
1180 [[maybe_unused]] TextDirection direction, Led_Point outputAt, CoordinateType hTabOrigin,
1181 const TabStopList& tabStopList, DistanceType* amountDrawn, CoordinateType hScrollOffset)
1182{
1183 DistanceType widthSoFar = 0;
1184 const Led_tChar* textCursor = text;
1185 const Led_tChar* textEnd = text + nBytes;
1186 while (textCursor < textEnd) {
1187 // Skip over tabs
1188 const Led_tChar* nextTabAt = textCursor;
1189 for (; nextTabAt < textEnd;) {
1190 if (*nextTabAt == '\t') {
1191 break;
1192 }
1193 ++nextTabAt;
1194 }
1195
1196// Actually image the characters
1197#if qStroika_Platform_Windows
1198 int oldBkMode = SetBkMode (TRANSPARENT);
1199
1200 if (direction == eLeftToRight) {
1201 Win32_TextOut (m_hDC, static_cast<int> (outputAt.h + widthSoFar - hScrollOffset), static_cast<int> (outputAt.v), textCursor,
1202 static_cast<int> (nextTabAt - textCursor));
1203
1204 // Geez! There must be SOME API within Win32 to give me this info (like SetTextAlign (UPDATE_CP))
1205 // without recomputing it. But there doesn't appear to be. So we must recompute!
1206 // LGP 960503
1207
1208 // A SLIGHT optimization is now possible here - we don't need to compute the last GetTextExtent() if amountDrawn
1209 // is nullptr (typically TRUE) - LGP 960521
1210 if (amountDrawn != nullptr or (nextTabAt < textEnd)) {
1211 SIZE size;
1212 Win32_GetTextExtentPoint (m_hAttribDC, textCursor, static_cast<int> (nextTabAt - textCursor), &size);
1213 widthSoFar += size.cx;
1214 }
1215 }
1216 else {
1217 /*
1218 * A bunch of different ways to get the RTL code emitted. Try them each in order (some ifdefed out). When
1219 * one succeeds - just to the succcess label.
1220 */
1221#if qUseGetCharPlacementToImage_
1222 {
1223 size_t len = nextTabAt - textCursor;
1224 Memory::StackBuffer<wchar_t> glyphs{len};
1225 GCP_RESULTSW gcpResult;
1226 memset (&gcpResult, 0, sizeof (gcpResult));
1227 gcpResult.lStructSize = sizeof (GCP_RESULTS);
1228 gcpResult.lpGlyphs = glyphs.data ();
1229 gcpResult.nGlyphs = static_cast<UINT> (len);
1230 if (::GetCharacterPlacementW (m_hDC, textCursor, static_cast<int> (len), 0, &gcpResult, GCP_GLYPHSHAPE | GCP_LIGATE) != 0) {
1231 Verify (::ExtTextOutW (m_hDC, outputAt.h + widthSoFar - hScrollOffset, outputAt.v, ETO_GLYPH_INDEX, nullptr,
1232 gcpResult.lpGlyphs, gcpResult.nGlyphs, nullptr));
1233 goto Succeeded_But_Need_To_Adjust_Width;
1234 }
1235 }
1236#endif
1237
1238 {
1239 size_t len = nextTabAt - textCursor;
1240 // Fallback - if the above fails...
1241 // Displays the text in the right order, but doesn't do contextual shaping (tested on WinXP and WinME) - LGP 2002-12-10
1242 Verify (::ExtTextOutW (m_hDC, outputAt.h + widthSoFar - hScrollOffset, outputAt.v, 0, nullptr, textCursor,
1243 static_cast<UINT> (len), nullptr));
1244 }
1245
1246 Succeeded_But_Need_To_Adjust_Width:
1247 // Geez! There must be SOME API within Win32 to give me this info (like SetTextAlign (UPDATE_CP))
1248 // without recomputing it. But there doesn't appear to be. So we must recompute!
1249 // LGP 960503
1250
1251 // A SLIGHT optimization is now possible here - we don't need to compute the last GetTextExtent() if amountDrawn
1252 // is nullptr (typically TRUE) - LGP 960521
1253 if (amountDrawn != nullptr or (nextTabAt < textEnd)) {
1254 SIZE size;
1255 Win32_GetTextExtentPoint (m_hAttribDC, textCursor, static_cast<int> (nextTabAt - textCursor), &size);
1256 widthSoFar += size.cx;
1257 }
1258 }
1259
1260 (void)SetBkMode (oldBkMode);
1261#endif
1262
1263 // Now see if nextTab really pointing at a tab (otherwise at end of buffer)
1264 if (nextTabAt < textEnd) {
1265 DistanceType thisTabWidth;
1266 {
1267 DistanceType curOutputAtZeroBased = (outputAt.h - hTabOrigin) + widthSoFar;
1268 DistanceType tabStop = tabStopList.ComputeTabStopAfterPosition (this, curOutputAtZeroBased);
1269 thisTabWidth = tabStop - curOutputAtZeroBased;
1270 Assert (thisTabWidth >= 0);
1271 }
1272
1273 widthSoFar += thisTabWidth;
1274 ++nextTabAt; // since we processed that tab...
1275 }
1276 textCursor = nextTabAt;
1277 }
1278 if (amountDrawn != nullptr) {
1279 *amountDrawn = widthSoFar;
1280 }
1281}
1282
1283void Tablet::SetBackColor (const Color& backColor)
1284{
1285#if qStroika_Platform_Windows
1286 SetBkColor (backColor.GetOSRep ());
1287#endif
1288}
1289
1290void Tablet::SetForeColor (const Color& foreColor)
1291{
1292#if qStroika_Platform_Windows
1293 SetTextColor (foreColor.GetOSRep ());
1294#endif
1295}
1296
1297/*
1298@METHOD: Tablet::EraseBackground_SolidHelper
1299@DESCRIPTION: <p>EraseBackground_SolidHelper () is simple helper function - usually called from subclasses which OVERRIDE
1300 @'TextImager::EraseBackground'.</p>
1301*/
1302void Tablet::EraseBackground_SolidHelper (const Led_Rect& eraseRect, const Color& eraseColor)
1303{
1304 if (not eraseRect.IsEmpty ()) {
1305#if qStroika_Platform_Windows
1306 Led_Rect eraser = eraseRect;
1307 Brush backgroundBrush (eraseColor.GetOSRep ());
1308 GDI_Obj_Selector pen (this, ::GetStockObject (NULL_PEN));
1309 GDI_Obj_Selector brush (this, backgroundBrush);
1310 ++eraser.right; // lovely - windows doesn't count last pixel... See Docs for Rectangle() and rephrase!!!
1311 ++eraser.bottom;
1312 Rectangle (AsRECT (eraser));
1313#endif
1314 }
1315}
1316
1317/*
1318@METHOD: Tablet::HilightArea_SolidHelper
1319@DESCRIPTION: <p>HilightArea_SolidHelper () is simple helper function - usually called from subclasses which OVERRIDE
1320 @'TextImager::HilightArea'.</p>
1321 <p>Note the backColor and foreColor are advisory - and maybe ignored if the GDI better supports (or the
1322 platform UI conventionally calls for) inverting the text via a simple XOR.</p>
1323 <p>Note also that as of Led 3.1b4, there is new code to properly respect the hilight fore/back colors. This code
1324 should generally be fast enough, but may - on some hardware - NOT be fast enough. By setting the text color to black,
1325 the background color to while, and the hilight colors the reverse of this (fore=black/back=white), this code will revert
1326 to the old algorithm, and run much faster.</p>
1327*/
1328void Tablet::HilightArea_SolidHelper (const Led_Rect& hilightArea, [[maybe_unused]] Color hilightBackColor,
1329 [[maybe_unused]] Color hilightForeColor, Color oldBackColor, [[maybe_unused]] Color oldForeColor)
1330{
1331 if (not hilightArea.IsEmpty ()) {
1332#if qStroika_Platform_Windows
1333 /*
1334 * SPR#1271 - major reworking using DIB sections etc, to get much better display of hilighted text.
1335 */
1336 if (hilightBackColor.GetOSRep () == Color::kBlack.GetOSRep () and hilightForeColor.GetOSRep () == Color::kWhite.GetOSRep () and
1337 oldBackColor.GetOSRep () == Color::kWhite.GetOSRep () and oldForeColor.GetOSRep () == Color::kBlack.GetOSRep ()) {
1338 // This is much faster (on some/most hardware) than the RecolorHelper algorithms. For this special case of of B&W fore/back/hilight
1339 // colors - this code is MUCH faster as well. So - for people for whom the default algorithm is too slow - they can just specify
1340 // these colors for hilight and back/fore-color, and they'll get the faster hilight.
1341 // See SPR#1271
1342 BitBlt (hilightArea.left, hilightArea.top, hilightArea.GetWidth (), hilightArea.GetHeight (), this, hilightArea.left,
1343 hilightArea.top, DSTINVERT);
1344 }
1345 else {
1346#if 1
1347 fRecolorHelper = RecolorHelper::CheckCacheAndReconstructIfNeeded (fRecolorHelper, m_hDC,
1348 Led_Size (hilightArea.GetHeight (), hilightArea.GetWidth ()),
1349 hilightBackColor, hilightForeColor, oldBackColor, oldForeColor);
1350 fRecolorHelper->DoRecolor (hilightArea);
1351#else
1352 static RecolorHelper* recolorHelper = nullptr;
1353 recolorHelper = RecolorHelper::CheckCacheAndReconstructIfNeeded (recolorHelper, m_hDC,
1354 Led_Size (hilightArea.GetHeight (), hilightArea.GetWidth ()),
1355 hilightBackColor, hilightForeColor, oldBackColor, oldForeColor);
1356 recolorHelper->DoRecolor (hilightArea);
1357#endif
1358 }
1359#endif
1360 }
1361}
1362
1363/*
1364@METHOD: Tablet::HilightArea_SolidHelper
1365@DESCRIPTION: <p>HilightArea_SolidHelper () is simple helper function - usually called from subclasses which OVERRIDE
1366 @'TextImager::HilightArea'.</p>
1367 <p>Note the backColor and foreColor are advisory - and maybe ignored if the GDI better supports (or the
1368 platform UI conventionally calls for) inverting the text via a simple XOR.</p>
1369*/
1370void Tablet::HilightArea_SolidHelper (const Region& hilightArea, [[maybe_unused]] Color hilightBackColor, [[maybe_unused]] Color hilightForeColor,
1371 [[maybe_unused]] Color oldBackColor, [[maybe_unused]] Color oldForeColor)
1372{
1373 if (not hilightArea.IsEmpty ()) {
1374#if qStroika_Platform_Windows
1375 Assert (false); // probably not hard - bit not totally obvious how todo and since not called yet - ignore for now... LGP 2002-12-03
1376#endif
1377 }
1378}
1379
1380/*
1381@METHOD: Tablet::GetFontMetrics
1382@DESCRIPTION: <p>Retrieve the (@'FontMetrics') associated with the current tablet (based on the last SetFont call).</p>
1383*/
1384FontMetrics Tablet::GetFontMetrics () const
1385{
1386#if qStroika_Platform_Windows
1387 RequireNotNull (m_hAttribDC);
1388 TEXTMETRIC tms;
1389 Verify (::GetTextMetrics (m_hAttribDC, &tms) != 0);
1390 return tms;
1391#endif
1392}
1393
1394/*
1395 ********************************************************************************
1396 ***************************** OffscreenTablet::OT ******************************
1397 ********************************************************************************
1398 */
1399#if qStroika_Platform_Windows
1400OffscreenTablet::OT::OT (HDC hdc, Tablet::OwnDCControl ownsDC)
1401 : inherited (hdc, ownsDC)
1402{
1403}
1404#endif
1405
1406/*
1407 ********************************************************************************
1408 ********************************* OffscreenTablet ******************************
1409 ********************************************************************************
1410 */
1411OffscreenTablet::OffscreenTablet ()
1412 : fOrigTablet (nullptr)
1413 , fOffscreenRect (Led_Rect (0, 0, 0, 0))
1414 , fOffscreenTablet (nullptr)
1415#if qStroika_Platform_Windows
1416 , fMemDC ()
1417 , fMemoryBitmap ()
1418 , fOldBitmapInDC (nullptr)
1419#endif
1420{
1421}
1422
1423OffscreenTablet::~OffscreenTablet ()
1424{
1425#if qStroika_Platform_Windows
1426 if (fOldBitmapInDC != nullptr) {
1427 (void)fMemDC.SelectObject (fOldBitmapInDC);
1428 }
1429#endif
1430}
1431
1432/*
1433@METHOD: OffscreenTablet::Setup
1434@DESCRIPTION: <p>Prepare a new offscreen drawing environment given the starting basis 'originalTablet' (typically from a window).</p>
1435 <p>Later call @'OffscreenTablet::PrepareRect' before any actual drawing can be done. This should be called once before
1436 calling @'OffscreenTablet::PrepareRect'.</p>
1437*/
1438void OffscreenTablet::Setup (Tablet* origTablet)
1439{
1440 Require (fOrigTablet == nullptr); // can only call once.
1441 RequireNotNull (origTablet);
1442
1443 fOrigTablet = origTablet;
1444#if qStroika_Platform_Windows
1445 if (fMemDC.CreateCompatibleDC (fOrigTablet)) {
1446 fOffscreenTablet = &fMemDC;
1447 }
1448#endif
1449}
1450
1451/*
1452@METHOD: OffscreenTablet::PrepareRect
1453@DESCRIPTION: <p>Prepare the offscreen drawing environment for the given 'currentRowRect'. This can only be safely called
1454 after the call to @'OffscreenTablet::Setup' - but can be called multiple times. Note that calls to this
1455 will typically 'destroy' the bits in the offscreen tablet.</p>
1456*/
1457Tablet* OffscreenTablet::PrepareRect (const Led_Rect& currentRowRect, DistanceType extraToAddToBottomOfRect)
1458{
1459 Tablet* result = fOrigTablet;
1460#if qStroika_Platform_Windows
1461 if (fOffscreenTablet != nullptr) {
1462 fOffscreenRect = currentRowRect;
1463 fOffscreenRect.bottom += extraToAddToBottomOfRect;
1464 // See if we need to re-allocate the bitmap
1465 if (fMemoryBitmap == nullptr or (fOffscreenRect.GetSize () != fMemoryBitmap.GetImageSize ())) {
1466 // deselect our new memory bitmap before changing its size - not sure needed, but lets be paranoid -
1467 // this is Windows after all ... LGP 960513
1468 if (fOldBitmapInDC != nullptr) {
1469 (void)fMemDC.SelectObject (fOldBitmapInDC);
1470 }
1471 fMemoryBitmap.DeleteObject (); // lose previous contents, if any...
1472#if qUseDIBSectionForOffscreenBitmap_
1473 if (fMemoryBitmap.CreateCompatibleDIBSection (fOrigTablet->m_hDC, fOffscreenRect.GetWidth (), fOffscreenRect.GetHeight ()) == 0) {
1474 fOffscreenTablet = nullptr; // OK, just don't use...
1475 }
1476#else
1477 if (fMemoryBitmap.CreateCompatibleBitmap (fOrigTablet->m_hDC, fOffscreenRect.GetWidth (), fOffscreenRect.GetHeight ()) == 0) {
1478 fOffscreenTablet = nullptr; // OK, just don't use...
1479 }
1480#endif
1481 if (fOffscreenTablet != nullptr) {
1482 fOldBitmapInDC = fMemDC.SelectObject (fMemoryBitmap);
1483 if (fOldBitmapInDC == nullptr) {
1484 fOffscreenTablet = nullptr; // OK, just don't use...
1485 }
1486 }
1487 }
1488 if (fOffscreenTablet != nullptr) {
1489 result = fOffscreenTablet; // Draw into offscreen bitmap
1490 }
1491 if (fOffscreenTablet != nullptr) {
1492 fMemDC.SetWindowOrg (fOffscreenRect.left, fOffscreenRect.top);
1493 }
1494 }
1495#endif
1496 return result;
1497}
1498
1499/*
1500@METHOD: OffscreenTablet::BlastBitmapToOrigTablet
1501@DESCRIPTION: <p>Copy the bits which have been saved away into this offscreen tablet back to the original tablet specified in
1502 @'OffscreenTablet::Setup' and to coordinates specified in the last call to @'OffscreenTablet::PrepareRect'.</p>
1503*/
1504void OffscreenTablet::BlastBitmapToOrigTablet ()
1505{
1506 if (fOffscreenTablet != nullptr) {
1507#if qStroika_Platform_Windows
1508 Tablet* screenDC = fOrigTablet;
1509 screenDC->BitBlt (fOffscreenRect.left, fOffscreenRect.top, fOffscreenRect.GetWidth (), fOffscreenRect.GetHeight (),
1510 fOffscreenTablet, fOffscreenRect.left, fOffscreenRect.top, SRCCOPY);
1511#endif
1512 }
1513}
1514#endif
1515
1516#if qStroika_Frameworks_Led_SupportGDI
1517
1518/*
1519 ********************************************************************************
1520 ********************************* InstalledFonts ***************************
1521 ********************************************************************************
1522 */
1523InstalledFonts::InstalledFonts (FilterOptions filterOptions)
1524 : fFilterOptions (filterOptions)
1525 , fFontNames ()
1526{
1527#if qStroika_Platform_Windows
1528 LOGFONT lf;
1529 memset (&lf, 0, sizeof (LOGFONT));
1530 lf.lfCharSet = DEFAULT_CHARSET;
1531 WindowDC screenDC (nullptr);
1532 ::EnumFontFamiliesEx (screenDC.m_hDC, &lf, (FONTENUMPROC)FontFamilyAdderProc, reinterpret_cast<LPARAM> (this), 0);
1533 sort (fFontNames.begin (), fFontNames.end ());
1534 vector<SDKString>::iterator rest = unique (fFontNames.begin (), fFontNames.end ());
1535 fFontNames.erase (rest, fFontNames.end ()); // remove the duplicates
1536#else
1537 Assert (false); // NYI for other platforms
1538#endif
1539}
1540
1541#if qStroika_Platform_Windows
1542BOOL FAR PASCAL InstalledFonts::FontFamilyAdderProc (ENUMLOGFONTEX* pelf, NEWTEXTMETRICEX* /*lpntm*/, int fontType, LPVOID pThis)
1543{
1544 InstalledFonts* thisP = reinterpret_cast<InstalledFonts*> (pThis);
1545
1546 if (thisP->fFilterOptions & eSkipRasterFonts) {
1547 // don't put in non-printer raster fonts (cuz WordPad doesn't and CFontDialog doesn't appear to -
1548 // LGP 971215)
1549 if (fontType & RASTER_FONTTYPE)
1550 return 1;
1551 }
1552 if (thisP->fFilterOptions & eSkipAtSignFonts) {
1553 if (pelf->elfLogFont.lfFaceName[0] == '@')
1554 return 1;
1555 }
1556 thisP->fFontNames.push_back (pelf->elfLogFont.lfFaceName);
1557 return 1;
1558}
1559#endif
1560#endif
1561
1562#if qStroika_Frameworks_Led_SupportGDI
1563
1564/*
1565 ********************************************************************************
1566 ********************************* Globals *******************************
1567 ********************************************************************************
1568 */
1569
1570Globals* Globals::sThe = nullptr;
1571
1572// Somewhat silly hack so Globals gets destroyed at end of application execution. Helpful for quitting memleak detectors.
1573class Globals::_Global_DESTRUCTOR_ {
1574public:
1575 ~_Global_DESTRUCTOR_ ()
1576 {
1577 delete (Globals::sThe);
1578 Globals::sThe = nullptr;
1579 }
1580} sTheLed_GDIGlobalsDESTRUCTOR_;
1581
1582Globals::Globals ()
1583 : fLogPixelsH (0)
1584 , fLogPixelsV (0)
1585{
1586 InvalidateGlobals ();
1587}
1588
1589void Globals::InvalidateGlobals ()
1590{
1591// From the name, it would appear we invalidated, and re-validate later. But I think this implematnion is a bit
1592// simpler, and should perform fine given its expected usage.
1593#if qStroika_Platform_Windows
1594 WindowDC screenDC (nullptr);
1595 fLogPixelsH = ::GetDeviceCaps (screenDC, LOGPIXELSX);
1596 fLogPixelsV = ::GetDeviceCaps (screenDC, LOGPIXELSY);
1597#endif
1598}
1599#endif
1600
1601#if qStroika_Frameworks_Led_SupportGDI
1602/*
1603 ********************************************************************************
1604 ************************************ AddRectangleToRegion **********************
1605 ********************************************************************************
1606 */
1607void Led::AddRectangleToRegion (Led_Rect addRect, Region* toRgn)
1608{
1609 RequireNotNull (toRgn);
1610 *toRgn = *toRgn + Region (addRect);
1611}
1612#endif
1613
1614/*
1615 ********************************************************************************
1616 ********************************* Led_GetDIBImageSize **************************
1617 ********************************************************************************
1618 */
1619
1620/*
1621@METHOD: Led_GetDIBImageSize
1622@DESCRIPTION: <p>Return the size in pixels of the given argument DIB</p>
1623*/
1624Led_Size Led::Led_GetDIBImageSize (const Led_DIB* dib)
1625{
1626 RequireNotNull (dib);
1627 Assert (sizeof (BITMAPINFOHEADER) == 40); // just to make sure we have these defined right on other platforms
1628 Assert (sizeof (BITMAPCOREHEADER) == 12); // ''
1629 Assert (sizeof (RGBTRIPLE) == 3); // ''
1630
1631 if (IS_WIN30_DIB (dib)) {
1632 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
1633 return (Led_Size (abs (Led_ByteSwapFromWindows (hdr.biHeight)), abs (Led_ByteSwapFromWindows (hdr.biWidth))));
1634 }
1635 else {
1636 const BITMAPCOREHEADER& hdr = *(reinterpret_cast<const BITMAPCOREHEADER*> (dib));
1637 return (Led_Size (Led_ByteSwapFromWindows (hdr.bcHeight), Led_ByteSwapFromWindows (hdr.bcWidth)));
1638 }
1639}
1640
1641/*
1642 ********************************************************************************
1643 *************************** Led_GetDIBPalletByteCount **************************
1644 ********************************************************************************
1645 */
1646size_t Led::Led_GetDIBPalletByteCount (const Led_DIB* dib)
1647{
1648 RequireNotNull (dib);
1649 /*
1650 * Logic from MSFT DibLook sample in MSVC.Net 2003, plus:
1651 * MSVC.Net 2003 SDK docs mention this case - that:
1652 * BI_BITFIELDS: Specifies that the bitmap is not compressed and that the
1653 * color table consists of three DWORD color masks that specify
1654 * the red, green, and blue components, respectively, of each pixel.
1655 * This is valid when used with 16- and 32-bpp bitmaps
1656 */
1657 if (IS_WIN30_DIB (dib)) {
1658 size_t byteCount = DIBNumColors (dib) * sizeof (RGBQUAD);
1659 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
1660 //unsigned short bitCount = Led_ByteSwapFromWindows (hdr.biBitCount);
1661 if (Led_ByteSwapFromWindows (hdr.biCompression) == BI_BITFIELDS) {
1662#if qStroika_Platform_Windows
1663 Assert (sizeof (DWORD) == sizeof (unsigned int));
1664#endif
1665 Assert (4 == sizeof (unsigned int));
1666 byteCount += 3 * sizeof (unsigned int);
1667 }
1668 return byteCount;
1669 }
1670 else {
1671 Assert (sizeof (RGBTRIPLE) == 3); // make sure we have this defined right on each platform
1672 return (DIBNumColors (dib) * sizeof (RGBTRIPLE));
1673 }
1674}
1675
1676/*
1677 ********************************************************************************
1678 ************************* Led_GetDIBImageRowByteCount **************************
1679 ********************************************************************************
1680 */
1681/*
1682@METHOD: Led_GetDIBImageRowByteCount
1683@DESCRIPTION: <p>Return the size in bytes of a single ROW of pixels in the given argument DIB.</p>
1684*/
1685size_t Led::Led_GetDIBImageRowByteCount (const Led_DIB* dib)
1686{
1687 RequireNotNull (dib);
1688 Led_Size imageSize = Led_GetDIBImageSize (dib);
1689 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
1690
1691 unsigned short bitCount = Led_ByteSwapFromWindows (hdr.biBitCount);
1692 return (((imageSize.h * bitCount + 31) & ~31) >> 3);
1693}
1694
1695/*
1696 ********************************************************************************
1697 *************************** Led_GetDIBImageByteCount ***************************
1698 ********************************************************************************
1699 */
1700/*
1701@METHOD: Led_GetDIBImageByteCount
1702@DESCRIPTION: <p>Return the size in bytes of the given argument DIB. DIBs are contiguous chunks of RAM.</p>
1703*/
1704size_t Led::Led_GetDIBImageByteCount (const Led_DIB* dib)
1705{
1706 RequireNotNull (dib);
1707 Led_Size imageSize = Led_GetDIBImageSize (dib);
1708 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
1709 size_t byteCount = Led_ByteSwapFromWindows (hdr.biSize);
1710
1711 byteCount += Led_GetDIBPalletByteCount (dib);
1712
1713 unsigned long imageByteSize = Led_ByteSwapFromWindows (hdr.biSizeImage);
1714 if (imageByteSize == 0) {
1715 unsigned short bitCount = Led_ByteSwapFromWindows (hdr.biBitCount);
1716 // often zero for uncompressed images, so we compute ourselves...
1717 //imageByteSize = imageSize.v * ((imageSize.h * bitCount + 31)/32)*4;
1718 imageByteSize = imageSize.v * (((imageSize.h * bitCount + 31) & ~31) >> 3);
1719 }
1720 byteCount += imageByteSize;
1721 return byteCount;
1722}
1723
1724/*
1725 ********************************************************************************
1726 ********************************* Led_CloneDIB *********************************
1727 ********************************************************************************
1728 */
1729/*
1730@METHOD: Led_CloneDIB
1731@DESCRIPTION: <p>Make a copy of the given @'Led_DIB' object using ::operator new (). Just use normal C++ ::operator delete ()
1732 to destroy the result.</p>
1733*/
1734Led_DIB* Led::Led_CloneDIB (const Led_DIB* dib)
1735{
1736 RequireNotNull (dib);
1737 size_t nBytes = Led_GetDIBImageByteCount (dib);
1738 Led_DIB* newDIB = reinterpret_cast<Led_DIB*> (new char[nBytes]);
1739 (void)::memcpy (newDIB, dib, nBytes);
1740 return newDIB;
1741}
1742
1743/*
1744 ********************************************************************************
1745 ***************************** Led_GetDIBBitsPointer ****************************
1746 ********************************************************************************
1747 */
1748/*
1749@METHOD: Led_GetDIBBitsPointer
1750@DESCRIPTION: <p></p>
1751*/
1752const void* Led::Led_GetDIBBitsPointer (const Led_DIB* dib)
1753{
1754 RequireNotNull (dib);
1755 const BITMAPINFOHEADER& hdr = dib->bmiHeader;
1756 return reinterpret_cast<const char*> (dib) + Led_ByteSwapFromWindows (hdr.biSize) + Led_GetDIBPalletByteCount (dib);
1757}
1758
1759#if qStroika_Platform_Windows
1760/*
1761 ********************************************************************************
1762 ****************************** Led_DIBFromHBITMAP ******************************
1763 ********************************************************************************
1764 */
1765Led_DIB* Led::Led_DIBFromHBITMAP (HDC hDC, HBITMAP hbm)
1766{
1767 RequireNotNull (hbm);
1768 BITMAP bm;
1769 Verify (::GetObject (hbm, sizeof (BITMAP), (LPVOID)&bm));
1770
1771 Led_DIB* dibResult = nullptr;
1772 {
1773 BITMAPINFOHEADER bmiHdr;
1774 memset (&bmiHdr, 0, sizeof (bmiHdr));
1775 bmiHdr.biSize = sizeof (BITMAPINFOHEADER);
1776 bmiHdr.biWidth = bm.bmWidth;
1777 bmiHdr.biHeight = bm.bmHeight;
1778 bmiHdr.biPlanes = 1;
1779 bmiHdr.biBitCount = 24;
1780 bmiHdr.biCompression = BI_RGB;
1781 bmiHdr.biSizeImage = ((((bmiHdr.biWidth * bmiHdr.biBitCount) + 31) & ~31) >> 3) * bmiHdr.biHeight;
1782 size_t nBytes = Led_GetDIBImageByteCount (reinterpret_cast<Led_DIB*> (&bmiHdr));
1783 dibResult = reinterpret_cast<Led_DIB*> (new char[nBytes]);
1784 Assert (nBytes > sizeof (BITMAPINFOHEADER));
1786 (void)::memcpy (dibResult, &bmiHdr, sizeof (bmiHdr));
1787 DISABLE_COMPILER_MSC_WARNING_END (6386)
1788 }
1789
1790 [[maybe_unused]] int nScanLinesCopied =
1791 ::GetDIBits (hDC, hbm, 0, dibResult->bmiHeader.biHeight,
1792 reinterpret_cast<char*> (dibResult) + Led_GetDIBPalletByteCount (dibResult) + sizeof (BITMAPINFOHEADER), dibResult, DIB_RGB_COLORS);
1793 Assert (nScanLinesCopied == dibResult->bmiHeader.biHeight);
1794 return dibResult;
1795}
1796#endif
1797
1798#if qStroika_Frameworks_Led_ProvideIMESupport
1799#include <ime.h>
1800
1801/*
1802 ********************************************************************************
1803 ****************************************** IME *********************************
1804 ********************************************************************************
1805 */
1806IME* IME::sThe = nullptr;
1807
1808#ifndef qUseNewIMECode_
1809#define qUseNewIMECode_ 1
1810#endif
1811
1812// Somewhat silly hack so IME gets destroyed at end of application execution. Helpful for quitting memleak detectors.
1813class IME::_Global_DESTRUCTOR_ {
1814public:
1815 ~_Global_DESTRUCTOR_ ()
1816 {
1817 delete (IME::sThe);
1818 IME::sThe = nullptr;
1819 }
1820} sTheIME_DESTRUCTOR_;
1821
1822IME::IME ()
1823 : fSendIMEMessageProc (nullptr)
1824 , fIMEEnableProc (nullptr)
1825 , fImmGetContext (nullptr)
1826 , fImmSetCompositionFont (nullptr)
1827 , fImmReleaseContext (nullptr)
1828 , fImmGetCompositionStringW (nullptr)
1829 , fImmSetCompositionWindow (nullptr)
1830 , fImmSetOpenStatus (nullptr)
1831 , fWinNlsAvailable (false)
1832 , fLastX (-1)
1833 , fLastY (-1)
1834{
1835 Assert (sThe == nullptr);
1836 sThe = this;
1837
1838#ifdef _UNICODE
1839 const char IMEPROCNAME[] = "SendIMEMessageExW";
1840#else
1841 const char IMEPROCNAME[] = "SendIMEMessageExA";
1842#endif
1843 HINSTANCE hNLS = ::GetModuleHandle (_T ("USER32.DLL"));
1844 if (hNLS != nullptr) {
1845 fSendIMEMessageProc = (LRESULT (FAR PASCAL*) (HWND, DWORD))::GetProcAddress (hNLS, IMEPROCNAME);
1846 fIMEEnableProc = (BOOL (FAR PASCAL*) (HWND, BOOL))::GetProcAddress (hNLS, "WINNLSEnableIME");
1847 }
1848 fWinNlsAvailable = fSendIMEMessageProc != nullptr and fIMEEnableProc != nullptr;
1849
1850 HINSTANCE hIMM = ::GetModuleHandle (_T ("IMM32.DLL"));
1851 if (hIMM != nullptr) {
1852#ifdef _UNICODE
1853 constexpr char ImmSetCompositionFontNAME[] = "ImmSetCompositionFontW";
1854#else
1855 constexpr char ImmSetCompositionFontNAME[] = "ImmSetCompositionFontA";
1856#endif
1857 fImmGetContext = (HIMC (FAR PASCAL*) (HWND))::GetProcAddress (hIMM, "ImmGetContext");
1858 fImmSetCompositionFont = (BOOL (FAR PASCAL*) (HIMC, const LOGFONT*))::GetProcAddress (hIMM, ImmSetCompositionFontNAME);
1859 fImmReleaseContext = (BOOL (FAR PASCAL*) (HWND, HIMC))::GetProcAddress (hIMM, "ImmReleaseContext");
1860 fImmGetCompositionStringW = (LONG (FAR PASCAL*) (HIMC, DWORD, LPVOID, DWORD))::GetProcAddress (hIMM, "ImmGetCompositionStringW");
1861 fImmSetCompositionWindow = (BOOL (FAR PASCAL*) (HIMC, const void*))::GetProcAddress (hIMM, "ImmSetCompositionWindow");
1862 fImmSetOpenStatus = (BOOL (FAR PASCAL*) (HIMC, BOOL))::GetProcAddress (hIMM, "ImmSetOpenStatus");
1863 }
1864}
1865
1866void IME::NotifyPosition (HWND hWnd, const SHORT x, const SHORT y)
1867{
1868 if (x != fLastX || y != fLastY) {
1869 UpdatePosition (hWnd, x, y);
1870 }
1871}
1872
1873void IME::NotifyOfFontChange (HWND hWnd, const LOGFONT& lf)
1874{
1875 if (fImmGetContext != nullptr and fImmSetCompositionFont != nullptr and fImmReleaseContext != nullptr) {
1876 HIMC hImc = NULL;
1877 if ((hImc = fImmGetContext (hWnd)) != NULL) {
1878 fImmSetCompositionFont (hImc, &lf);
1879 fImmReleaseContext (hWnd, hImc);
1880 }
1881 }
1882}
1883
1884#if !qUseNewIMECode_
1885void IME::SendSimpleMessage (HWND hWnd, UINT fnc, WPARAM wParam)
1886{
1887 if (fSendIMEMessageProc != nullptr) {
1888 HANDLE hime = ::GlobalAlloc (GMEM_MOVEABLE | GMEM_LOWER | GMEM_DDESHARE, (DWORD)sizeof (IMESTRUCT));
1889 LPIMESTRUCT lpime;
1890 if (hime)
1891 lpime = (LPIMESTRUCT)GlobalLock (hime);
1892 else
1893 return;
1894
1895 if (lpime == nullptr) {
1896 GlobalFree (hime);
1897 return;
1898 }
1899 lpime->fnc = fnc;
1900 lpime->wParam = wParam;
1901 fSendIMEMessageProc (hWnd, (LONG)hime);
1902 wParam = lpime->wParam;
1903 ::GlobalUnlock (hime);
1904 ::GlobalFree (hime);
1905 }
1906}
1907#endif
1908
1909void IME::IMEOn (HWND hWnd)
1910{
1911#if qUseNewIMECode_
1912 if (fImmGetContext != nullptr and fImmSetOpenStatus != nullptr and fImmReleaseContext != nullptr) {
1913 HIMC hImc = NULL;
1914 if ((hImc = fImmGetContext (hWnd)) != NULL) {
1915 Verify (fImmSetOpenStatus (hImc, true));
1916 fImmReleaseContext (hWnd, hImc);
1917 }
1918 }
1919#else
1920 SendSimpleMessage (hWnd, IME_SETOPEN, 1);
1921#endif
1922}
1923
1924void IME::IMEOff (HWND hWnd)
1925{
1926#if qUseNewIMECode_
1927 if (fImmGetContext != nullptr and fImmSetOpenStatus != nullptr and fImmReleaseContext != nullptr) {
1928 HIMC hImc = NULL;
1929 if ((hImc = fImmGetContext (hWnd)) != NULL) {
1930 Verify (fImmSetOpenStatus (hImc, false));
1931 fImmReleaseContext (hWnd, hImc);
1932 }
1933 }
1934#else
1935 SendSimpleMessage (hWnd, IME_SETOPEN, 0);
1936#endif
1937}
1938
1939void IME::UpdatePosition (const HWND hWnd, const SHORT x, const SHORT y)
1940{
1941 if (fSendIMEMessageProc != nullptr) {
1942#if qUseNewIMECode_
1943 if (fImmGetContext != nullptr and fImmSetCompositionWindow != nullptr and fImmReleaseContext != nullptr) {
1944 HIMC hImc = NULL;
1945 if ((hImc = fImmGetContext (hWnd)) != NULL) {
1946 COMPOSITIONFORM compForm;
1947 memset (&compForm, 0, sizeof (compForm));
1948 compForm.dwStyle = CFS_FORCE_POSITION;
1949 compForm.ptCurrentPos.x = x;
1950 compForm.ptCurrentPos.y = y;
1951 Verify (fImmSetCompositionWindow (hImc, &compForm));
1952 fImmReleaseContext (hWnd, hImc);
1953 fLastX = x;
1954 fLastY = y;
1955 }
1956 }
1957#else
1958 HANDLE hime = ::GlobalAlloc (GMEM_MOVEABLE | GMEM_LOWER | GMEM_DDESHARE, (DWORD)sizeof (IMESTRUCT));
1959 LPIMESTRUCT lpime = nullptr;
1960 if (hime != nullptr) {
1961 lpime = (LPIMESTRUCT)GlobalLock (hime);
1962 }
1963 else {
1964 return;
1965 }
1966
1967 if (lpime == nullptr) {
1968 return;
1969 }
1970
1971 lpime->fnc = IME_SETCONVERSIONWINDOW; // called IME_MOVECONVERTWINDOW in Win3.1
1972 lpime->wParam = MCW_WINDOW;
1973 lpime->wCount = 0;
1974 lpime->dchSource = 0;
1975 lpime->dchDest = 0;
1976 lpime->lParam1 = MAKELONG (x, y);
1977 lpime->lParam2 = 0L;
1978 lpime->lParam3 = 0L;
1979
1980 // SendIMEMessageProc returns 0 if there is an error, in which case
1981 // the error code is returned in IMESTRUCT.wParam;
1982 short ret = fSendIMEMessageProc (hWnd, (LONG)hime);
1983 ret = ret ? 0 : (short)lpime->wParam;
1984
1985 ::GlobalUnlock (hime);
1986 ::GlobalFree (hime);
1987 if (!ret) {
1988 fLastX = x;
1989 fLastY = y;
1990 }
1991#endif
1992
1993 // Should I redo this taking the LOGFONT as an arg to IME::UpdatePosition ()??? LGP 980714
1994 if (fImmGetContext != nullptr and fImmSetCompositionFont != nullptr and fImmReleaseContext != nullptr) {
1995 HFONT hFont = nullptr;
1996 if ((hFont = (HFONT)::SendMessage (hWnd, WM_GETFONT, 0, 0L)) != nullptr) {
1997 LOGFONT lFont;
1998 if (::GetObject (hFont, sizeof (LOGFONT), &lFont)) {
1999 HIMC hImc = NULL;
2000 if ((hImc = fImmGetContext (hWnd)) != NULL) {
2001 fImmSetCompositionFont (hImc, &lFont);
2002 fImmReleaseContext (hWnd, hImc);
2003 }
2004 }
2005 }
2006 }
2007 }
2008}
2009
2010wstring IME::GetCompositionResultStringW (HWND hWnd)
2011{
2012 wstring result;
2013 if (fImmGetCompositionStringW != nullptr and fImmGetContext != nullptr and fImmReleaseContext != nullptr) {
2014 HIMC hImc = 0;
2015 if ((hImc = fImmGetContext (hWnd)) != 0) {
2016 wchar_t curIMEString[2048];
2017 LONG nChars = fImmGetCompositionStringW (hImc, GCS_RESULTSTR, curIMEString, static_cast<DWORD> (std::size (curIMEString)));
2018
2019 nChars /= sizeof (wchar_t); // why???? LGP 991214
2020 if (nChars >= 0 and static_cast<size_t> (nChars) < std::size (curIMEString)) {
2021 curIMEString[nChars] = '\0';
2022 }
2023 else {
2024 curIMEString[0] = '\0';
2025 }
2026 result = curIMEString;
2027 fImmReleaseContext (hWnd, hImc);
2028 }
2029 }
2030 return result;
2031}
2032#endif
2033
2034Led_Rect Led::CenterRectInRect (const Led_Rect& r, const Led_Rect& centerIn)
2035{
2036 CoordinateType xLeft = (centerIn.left + centerIn.right) / 2 - r.GetWidth () / 2;
2037 CoordinateType yTop = (centerIn.top + centerIn.bottom) / 2 - r.GetHeight () / 2;
2038 return Led_Rect (yTop, xLeft, r.GetHeight (), r.GetWidth ());
2039}
2040
2041#if qStroika_Platform_Windows
2042void Led::Led_CenterWindowInParent (HWND w)
2043{
2044 Assert (::IsWindow (w));
2045 HWND hWndCenter = ::GetWindow (w, GW_OWNER);
2046 if (hWndCenter == nullptr) {
2047 hWndCenter = ::GetDesktopWindow ();
2048 }
2049 Assert (::IsWindow (hWndCenter));
2050
2051 // get coordinates of the window relative to its parent
2052 RECT rcDlg;
2053 ::GetWindowRect (w, &rcDlg);
2054 RECT rcCenter;
2055 ::GetWindowRect (hWndCenter, &rcCenter);
2056
2057 // find dialog's upper left based on rcCenter
2058 int xLeft = (rcCenter.left + rcCenter.right) / 2 - AsLedRect (rcDlg).GetWidth () / 2;
2059 int yTop = (rcCenter.top + rcCenter.bottom) / 2 - AsLedRect (rcDlg).GetHeight () / 2;
2060
2061 // map screen coordinates to child coordinates
2062 ::SetWindowPos (w, nullptr, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
2063}
2064#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:350
#define Verify(c)
Definition Assertions.h:422
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
basic_string< SDKChar > SDKString
Definition SDKString.h:38
conditional_t< qStroika_Foundation_Characters_SDKUseswchar_t, wchar_t, char > SDKChar
Definition SDKChar.h:77