2025-11-28 00:35:46 +09:00

1065 lines
27 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
//----------------------------------------------------------------------------
#include "Common.h"
#include "RenderTest.h"
#include "IRenderer.h"
#include "TextHelpers.h"
#include "resource.h"
// Uncomment the following definition to force the DPI to a hard-coded value for testing
// purposes. Otherwise the system default DPI is used.
//#define FORCE_DPI 120
// Global Variables:
HINSTANCE g_instance;
int g_dpiX;
int g_dpiY;
float const g_formatWidth = 6.0f * 96;
IDWriteFactory* g_dwriteFactory = NULL;
ID2D1Factory* g_d2dFactory = NULL;
namespace
{
// Global variables used only in this module.
DWRITE_MEASURING_MODE g_measuringMode = DWRITE_MEASURING_MODE_NATURAL;
IDWriteTextFormat* g_textFormat = NULL;
std::wstring g_text;
const static wchar_t g_defaultText[] =
L"ClearType is a software technology developed by Microsoft that improves the "
L"readability of text on existing LCDs (Liquid Crystal Displays), such as laptop "
L"screens, Pocket PC screens and flat panel monitors. With ClearType font technology, "
L"the words on your computer screen look almost as sharp and clear as those printed "
L"on a piece of paper.";
// Current monitor.
HMONITOR g_monitor;
// Current font.
inline float PointsToDips(float points)
{
return points * (96.0f / 72);
}
wchar_t const g_defaultFamilyName[] = L"Times New Roman";
float const g_minFontSize = PointsToDips(4);
float g_fontSize = PointsToDips(12);
LOGFONT g_logFont;
// Current angle of rotation.
int g_degrees = 0;
// Current renderer.
enum RendererID
{
RendererDWrite,
RendererD2D
};
RendererID g_rendererID = RendererDWrite;
IRenderer* g_renderer = NULL;
// Current magnifier state.
MagnifierInfo g_magnifier =
{
true, // visible
MagnifierInfo::Pixel, // type
3, // scale
0
};
bool g_dragging;
POINT g_dragPos;
// The following variables are used for the translate animation that occurs
// on the Nudge Text Left and Nudge Text Right commands.
DWORD g_animationStartCount;
float g_animationDuration;
float g_animationStartX;
float g_animationEndX;
float g_animationCurrentX;
}
// Forward declarations of functions included in this code module:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void InitializeMenuItems(HMENU popupMenu);
bool OnCommand(HWND hwnd, WORD commandID);
void UpdateAnimation(HWND hwnd);
bool IsAnimating();
HRESULT CreateRenderer(HWND hwnd);
void OnPaint(HWND hwnd);
void OnSize(HWND hwnd);
void OnMouseDown(HWND hwnd);
void OnMouseMove(HWND hwnd);
void OnMouseUp(HWND hwnd);
HRESULT OnChooseFont(HWND hwnd);
HRESULT IncreaseFontSize(HWND hwnd);
HRESULT DecreaseFontSize(HWND hwnd);
HRESULT SetFontSize(HWND hwnd, float newFontSize);
void SetMagnifierType(HWND hwnd, MagnifierInfo::Type newType);
void SetMagnifierScale(HWND hwnd, int scale);
void SetMeasuringMode(HWND hwnd, DWRITE_MEASURING_MODE newMode);
void SetTransform(HWND hwnd);
void SetRenderer(HWND hwnd, RendererID id);
HRESULT OnCopy(HWND hwnd);
HRESULT OnPaste(HWND hwnd);
void SetCaption(HWND hwnd);
int APIENTRY wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR commandLine,
int nCmdShow
)
{
// The Microsoft Security Development Lifecycle recommends that all
// applications include the following call to ensure that heap corruptions
// do not go unnoticed and therefore do not introduce opportunities
// for security exploits.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
HRESULT hr = S_OK;
g_instance = hInstance;
// Get the DPI.
#ifdef FORCE_DPI
g_dpiX = FORCE_DPI;
g_dpiY = FORCE_DPI;
#else
HDC hdc = GetDC(NULL);
g_dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
g_dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
#endif
// We're going to make the client area 7 x 5 inches initially.
int const clientWidth = 7 * g_dpiX;
int const clientHeight = 5 * g_dpiY;
// Initialize the magnifier size and center the focus rectangle in the client area.
g_magnifier.magnifierSize.cx = g_dpiX * 3;
g_magnifier.magnifierSize.cy = g_dpiY * 3 / 2;
g_magnifier.focusPos.x = (clientWidth - (g_magnifier.magnifierSize.cx / g_magnifier.scale)) / 2;
g_magnifier.focusPos.y = (clientHeight - (g_magnifier.magnifierSize.cy / g_magnifier.scale)) / 2;
try
{
g_text.assign(g_defaultText);
}
catch (...)
{
hr = ExceptionToHResult(); // Do not propagate exceptions up.
}
// Create the factory objects.
if (SUCCEEDED(hr))
{
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&g_dwriteFactory)
);
}
if (SUCCEEDED(hr))
{
hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory),
NULL,
(IID_PPV_ARGS(&g_d2dFactory))
);
}
if (SUCCEEDED(hr))
{
// Initialize the LOGFONT and use it to construct the text format object.
// We use LOGFONT in this application only because we rely on the Win32
// common font dialog.
memset(&g_logFont, 0, sizeof(g_logFont));
g_logFont.lfWeight = FW_NORMAL;
g_logFont.lfCharSet = DEFAULT_CHARSET;
g_logFont.lfOutPrecision = OUT_OUTLINE_PRECIS;
memcpy(g_logFont.lfFaceName, g_defaultFamilyName, sizeof(g_defaultFamilyName));
SafeRelease(&g_textFormat);
hr = CreateTextFormatFromLOGFONT(g_logFont, g_fontSize, &g_textFormat);
}
ATOM classAtom = 0;
if (SUCCEEDED(hr))
{
// Register the window class.
WNDCLASSEX wcex = { sizeof(wcex), 0 };
wcex.style = CS_HREDRAW|CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_DWRITERENDERTEST);
wcex.lpszClassName = L"RenderTest";
classAtom = RegisterClassEx(&wcex);
if (classAtom == 0)
hr = HRESULT_FROM_WIN32(GetLastError());
}
HACCEL accelerators = NULL;
if (SUCCEEDED(hr))
{
// Load the accelerator table.
accelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
if (accelerators == NULL)
hr = HRESULT_FROM_WIN32(GetLastError());
}
HWND hwnd = NULL;
if (SUCCEEDED(hr))
{
// Compute a window size that will give us our desired client size.
RECT windowRect = { 0, 0, clientWidth, clientHeight };
AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, TRUE);
// Create the window.
hwnd = CreateWindow(
MAKEINTATOM(classAtom),
L"", // caption (we'll set it later)
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL)
hr = HRESULT_FROM_WIN32(GetLastError());
}
MSG msg = {};
if (SUCCEEDED(hr))
{
SetCaption(hwnd);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, accelerators, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
delete g_renderer; g_renderer = NULL;
SafeRelease(&g_textFormat);
SafeRelease(&g_d2dFactory);
SafeRelease(&g_dwriteFactory);
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
if (OnCommand(hwnd, LOWORD(wParam)))
break;
else
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_INITMENUPOPUP:
InitializeMenuItems(reinterpret_cast<HMENU>(wParam));
break;
case WM_PAINT:
OnPaint(hwnd);
break;
case WM_ERASEBKGND:
return true;
case WM_SIZE:
OnSize(hwnd);
break;
case WM_WINDOWPOSCHANGED:
{
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (monitor != g_monitor)
{
g_monitor = monitor;
if (g_renderer != NULL)
g_renderer->SetMonitor(g_monitor);
InvalidateRect(hwnd, NULL, TRUE);
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_LBUTTONDOWN:
OnMouseDown(hwnd);
break;
case WM_MOUSEMOVE:
OnMouseMove(hwnd);
break;
case WM_LBUTTONUP:
OnMouseUp(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
void InitializeMenuItems(HMENU popupMenu)
{
int count = GetMenuItemCount(popupMenu);
for (int i = 0; i < count; ++i)
{
MENUITEMINFO info;
info.cbSize = sizeof(info);
info.fMask = MIIM_ID | MIIM_STATE;
if (!GetMenuItemInfo(popupMenu, i, TRUE, &info))
break;
bool check = false;
bool disable = false;
switch (info.wID)
{
case ID_EDIT_COPY:
disable = g_text.empty();
break;
case ID_EDIT_PASTE:
disable = !IsClipboardFormatAvailable(CF_TEXT);
break;
case ID_EDIT_DECREASETEXTSIZE:
disable = (g_fontSize == g_minFontSize);
break;
case ID_VIEW_SHOWMAGNIFIER:
check = g_magnifier.visible;
break;
case ID_MAGNIFIERTYPE_VECTOR:
check = (g_magnifier.type == MagnifierInfo::Vector);
disable = (g_measuringMode != DWRITE_MEASURING_MODE_NATURAL);
break;
case ID_MAGNIFIERTYPE_PIXELS:
check = (g_magnifier.type == MagnifierInfo::Pixel);
break;
case ID_MAGNIFIERTYPE_SUBPIXELS:
check = (g_magnifier.type == MagnifierInfo::Subpixel);
disable = (g_rendererID == RendererD2D);
break;
case ID_MAGNIFIERSCALE_3X:
check = (g_magnifier.scale == 3);
break;
case ID_MAGNIFIERSCALE_6X:
check = (g_magnifier.scale == 6);
break;
case ID_OPTIONS_NATURALMODE:
check = (g_measuringMode == DWRITE_MEASURING_MODE_NATURAL);
break;
case ID_OPTIONS_GDICLASSICMODE:
check = (g_measuringMode == DWRITE_MEASURING_MODE_GDI_CLASSIC);
break;
case ID_OPTIONS_GDINATURALMODE:
check = (g_measuringMode == DWRITE_MEASURING_MODE_GDI_NATURAL);
break;
case ID_OPTIONS_USEDIRECT2D:
check = (g_rendererID == RendererD2D);
break;
case ID_OPTIONS_USEDIRECTWRITE:
check = (g_rendererID == RendererDWrite);
break;
}
UINT newState =
(check ? MFS_CHECKED : 0) |
(disable ? MFS_DISABLED : 0);
if (newState != info.fState)
{
info.fMask = MIIM_STATE;
info.fState = newState;
SetMenuItemInfo(popupMenu, i, TRUE, &info);
}
}
}
bool OnCommand(HWND hwnd, WORD commandID)
{
HRESULT hr = S_OK;
switch (commandID)
{
case IDM_EXIT:
DestroyWindow(hwnd);
break;
case ID_EDIT_COPY:
OnCopy(hwnd);
break;
case ID_EDIT_PASTE:
OnPaste(hwnd);
break;
case ID_EDIT_FONT:
hr = OnChooseFont(hwnd);
break;
case ID_EDIT_INCREASETEXTSIZE:
hr = IncreaseFontSize(hwnd);
break;
case ID_EDIT_DECREASETEXTSIZE:
hr = DecreaseFontSize(hwnd);
break;
case ID_VIEW_ROTATELEFT:
if ((g_degrees -= 3) < 0)
g_degrees += 360;
SetTransform(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;
case ID_VIEW_ROTATERIGHT:
if ((g_degrees += 3) >= 360)
g_degrees -= 360;
SetTransform(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;
case ID_VIEW_NUDGELEFT:
g_animationStartCount = GetTickCount();
g_animationDuration = 0.1f;
g_animationStartX = g_animationCurrentX;
g_animationEndX = g_animationCurrentX - 10.0f;
InvalidateRect(hwnd, NULL, TRUE);
break;
case ID_VIEW_NUDGERIGHT:
g_animationStartCount = GetTickCount();
g_animationDuration = 0.1f;
g_animationStartX = g_animationCurrentX;
g_animationEndX = g_animationCurrentX + 10.0f;
InvalidateRect(hwnd, NULL, TRUE);
break;
case ID_VIEW_SHOWMAGNIFIER:
g_magnifier.visible = !g_magnifier.visible;
if (g_renderer != NULL)
{
g_renderer->SetMagnifier(g_magnifier);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case ID_MAGNIFIERTYPE_VECTOR:
SetMagnifierType(hwnd, MagnifierInfo::Vector);
break;
case ID_MAGNIFIERTYPE_PIXELS:
SetMagnifierType(hwnd, MagnifierInfo::Pixel);
break;
case ID_MAGNIFIERTYPE_SUBPIXELS:
SetMagnifierType(hwnd, MagnifierInfo::Subpixel);
break;
case ID_MAGNIFIERSCALE_3X:
SetMagnifierScale(hwnd, 3);
break;
case ID_MAGNIFIERSCALE_6X:
SetMagnifierScale(hwnd, 6);
break;
case ID_OPTIONS_NATURALMODE:
SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_NATURAL);
break;
case ID_OPTIONS_GDICLASSICMODE:
SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_GDI_CLASSIC);
break;
case ID_OPTIONS_GDINATURALMODE:
SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_GDI_NATURAL);
break;
case ID_OPTIONS_USEDIRECT2D:
SetRenderer(hwnd, RendererD2D);
break;
case ID_OPTIONS_USEDIRECTWRITE:
SetRenderer(hwnd, RendererDWrite);
break;
default:
return false;
}
if (FAILED(hr))
PostQuitMessage(hr);
return true;
}
HRESULT IncreaseFontSize(HWND hwnd)
{
float newFontSize = (g_fontSize < PointsToDips(24)) ?
g_fontSize + PointsToDips(0.5f) :
g_fontSize * 1.25f;
return SetFontSize(hwnd, newFontSize);
}
HRESULT DecreaseFontSize(HWND hwnd)
{
float newFontSize = (g_fontSize <= PointsToDips(24)) ?
g_fontSize - PointsToDips(0.5f) :
g_fontSize * (1/1.25f);
return SetFontSize(hwnd, std::max(newFontSize, g_minFontSize));
}
HRESULT SetFontSize(HWND hwnd, float newFontSize)
{
IDWriteTextFormat* newTextFormat = NULL;
HRESULT hr = CreateTextFormatFromLOGFONT(g_logFont, newFontSize, &newTextFormat);
if (FAILED(hr))
{
return (hr == DWRITE_E_NOFONT) ? S_OK : hr;
}
g_fontSize = newFontSize;
SafeSet(&g_textFormat, newTextFormat);
if (g_renderer != NULL)
{
g_renderer->SetFormat(g_textFormat);
}
InvalidateRect(hwnd, NULL, TRUE);
SafeRelease(&newTextFormat);
return S_OK;
}
HRESULT OnChooseFont(HWND hwnd)
{
HRESULT hr = S_OK;
LOGFONT logFont = g_logFont;
CHOOSEFONT font = { 0 };
font.lStructSize = sizeof(font);
font.hwndOwner = hwnd;
font.lpLogFont = &logFont;
font.iPointSize = static_cast<int>(g_fontSize * (720 / 96.0f));
// Don't show vertical fonts because we don't do vertical layout and don't show
// bitmap fonts because DirectWrite doesn't support them.
font.Flags = CF_SCREENFONTS | CF_SCALABLEONLY | CF_NOVERTFONTS | CF_INITTOLOGFONTSTRUCT;
// Show the common font dialog box.
if (ChooseFont(&font))
{
// The lfFaceName might not be initialized if the user didn't select a face name.
if (logFont.lfFaceName[0] == L'\0')
memcpy(logFont.lfFaceName, g_logFont.lfFaceName, sizeof(logFont.lfFaceName));
float newFontSize = font.iPointSize * (96.0f / 720);
// Map the Win32 font properties to an IDWriteTextFormat.
IDWriteTextFormat* newTextFormat = NULL;
HRESULT hr = CreateTextFormatFromLOGFONT(logFont, newFontSize, &newTextFormat);
if (SUCCEEDED(hr))
{
// Save the new font properties.
g_logFont = logFont;
g_fontSize = newFontSize;
SafeAttach(&g_textFormat, SafeDetach(&newTextFormat));
if (g_renderer != NULL)
{
g_renderer->SetFormat(g_textFormat);
}
InvalidateRect(hwnd, NULL, TRUE);
}
SafeRelease(&newTextFormat);
}
// Potentially expected error, but not fatal,
// so just do nothing.
if (hr == DWRITE_E_NOFONT)
hr = S_OK;
return hr;
}
void SetMagnifierType(HWND hwnd, MagnifierInfo::Type newType)
{
if (newType != g_magnifier.type)
{
if (newType == MagnifierInfo::Vector && g_measuringMode != DWRITE_MEASURING_MODE_NATURAL)
return;
g_magnifier.type = newType;
if (g_magnifier.visible && g_renderer != NULL)
{
g_renderer->SetMagnifier(g_magnifier);
InvalidateRect(hwnd, NULL, TRUE);
}
}
}
void SetMagnifierScale(HWND hwnd, int scale)
{
if (scale != g_magnifier.scale)
{
g_magnifier.scale = scale;
if (g_magnifier.visible && g_renderer != NULL)
{
g_renderer->SetMagnifier(g_magnifier);
InvalidateRect(hwnd, NULL, TRUE);
}
}
}
void SetMeasuringMode(HWND hwnd, DWRITE_MEASURING_MODE newMode)
{
if (newMode != g_measuringMode)
{
if (g_renderer != NULL)
{
g_renderer->SetMeasuringMode(newMode);
}
g_measuringMode = newMode;
if (newMode != DWRITE_MEASURING_MODE_NATURAL && g_magnifier.type == MagnifierInfo::Vector)
{
SetMagnifierType(hwnd, MagnifierInfo::Pixel);
}
InvalidateRect(hwnd, NULL, TRUE);
SetCaption(hwnd);
}
}
void SetRenderer(HWND hwnd, RendererID id)
{
if (id != g_rendererID)
{
delete g_renderer; g_renderer = NULL;
g_rendererID = id;
if (id == RendererD2D && (g_magnifier.type == MagnifierInfo::Subpixel))
{
SetMagnifierType(hwnd, MagnifierInfo::Pixel);
}
InvalidateRect(hwnd, NULL, TRUE);
SetCaption(hwnd);
}
}
void SetTransform(HWND hwnd)
{
if (g_renderer != NULL)
{
// Compute the center of the client area in DIPs.
RECT clientRect;
GetClientRect(hwnd, &clientRect);
float centerX = PixelsToDipsX(clientRect.right) * 0.5f;;
float centerY = PixelsToDipsY(clientRect.bottom) * 0.5f;
// Create a transform that rotates around the center point.
DWRITE_MATRIX transform = MakeRotateTransform(float(g_degrees), centerX, centerY);
// Add the current horizontal displacement.
transform.dx += g_animationCurrentX;
// Set the transform.
g_renderer->SetTransform(transform);
}
}
HRESULT OnCopy(HWND hwnd)
{
HRESULT hr = E_FAIL;
if (OpenClipboard(hwnd))
{
if (EmptyClipboard())
{
size_t const byteCount = (g_text.size() + 1) * sizeof(wchar_t);
HGLOBAL handle = GlobalAlloc(GMEM_DDESHARE, byteCount);
void* data = GlobalLock(handle);
if (data != NULL)
{
memcpy(data, g_text.c_str(), byteCount);
GlobalUnlock(handle);
}
if (SetClipboardData(CF_UNICODETEXT, handle) == NULL)
{
hr = S_OK;
GlobalFree(handle);
}
}
CloseClipboard();
}
return hr;
}
HRESULT OnPaste(HWND hwnd)
{
HRESULT hr = E_FAIL;
bool textChanged = false;
if (OpenClipboard(hwnd))
{
HGLOBAL handle = GetClipboardData(CF_UNICODETEXT);
if (handle != NULL)
{
size_t const maxLength = GlobalSize(handle) / sizeof(wchar_t);
wchar_t const* begin = static_cast<wchar_t const*>(GlobalLock(handle));
if (begin != NULL)
{
wchar_t const* end = std::find(begin, begin + maxLength, L'\0');
try
{
g_text.assign(begin, end);
textChanged = true;
hr = S_OK;
}
catch (...)
{
hr = ExceptionToHResult(); // Do not propagate exceptions up.
}
GlobalUnlock(handle);
}
}
CloseClipboard();
}
if (textChanged && g_renderer != NULL)
{
g_renderer->SetText(g_text.c_str());
InvalidateRect(hwnd, NULL, TRUE);
}
return hr;
}
bool IsAnimating()
{
return g_animationStartX != g_animationEndX;
}
void UpdateAnimation(HWND hwnd)
{
if (!IsAnimating())
return;
// Compute the elapsed time since the start of the animation in seconds.
DWORD const tickCount = GetTickCount();
float elapsed = (tickCount - g_animationStartCount) * (1.0f / 1000);
if (elapsed < g_animationDuration)
{
// We're still animating. Compuate the current x by interpolating between
// the start x and end x.
float r = elapsed / g_animationDuration;
g_animationCurrentX = (g_animationStartX * (1 - r)) + (g_animationEndX * r);
}
else
{
// We're done with this animation. Let both the current and start x equal the end x.
g_animationCurrentX = g_animationEndX;
g_animationStartX = g_animationEndX;
// If we're not at zero, we'll start a new animation ending at zero. The duration of
// this animation depends on the distance.
g_animationEndX = 0;
g_animationStartCount = tickCount;
g_animationDuration = fabs(g_animationStartX) / 10;
}
SetTransform(hwnd);
}
HRESULT CreateRenderer(HWND hwnd)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
delete g_renderer; g_renderer = NULL;
if (g_rendererID == RendererD2D)
{
g_renderer = CreateD2DRenderer(
hwnd,
clientRect.right,
clientRect.bottom,
g_textFormat,
g_text.c_str()
);
}
else
{
g_renderer = CreateDWriteRenderer(
hwnd,
clientRect.right,
clientRect.bottom,
g_textFormat,
g_text.c_str()
);
}
if (g_renderer == NULL)
return E_FAIL;
g_renderer->SetMeasuringMode(g_measuringMode);
if (g_monitor != NULL)
g_renderer->SetMonitor(g_monitor);
g_renderer->SetMagnifier(g_magnifier);
SetTransform(hwnd);
return S_OK;
}
void OnPaint(HWND hwnd)
{
HRESULT hr = S_OK;
// Update the current state.
UpdateAnimation(hwnd);
if (g_renderer == NULL)
{
hr = CreateRenderer(hwnd);
}
// Paint the current frame.
if (SUCCEEDED(hr))
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SetLayout(ps.hdc, LAYOUT_BITMAPORIENTATIONPRESERVED);
hr = g_renderer->Draw(hdc);
EndPaint(hwnd, &ps);
}
if (FAILED(hr))
{
PostQuitMessage(hr);
return;
}
// Invalidate if we're animating.
if (IsAnimating())
{
InvalidateRect(hwnd, NULL, TRUE);
}
}
void OnSize(HWND hwnd)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
// Reposition the magnifier.
g_magnifier.magnifierPos.x = clientRect.right - g_magnifier.magnifierSize.cx - (g_dpiX / 8);
g_magnifier.magnifierPos.y = clientRect.bottom - g_magnifier.magnifierSize.cy - (g_dpiY / 8);
// Update the renderer if it exists.
if (g_renderer != NULL)
{
g_renderer->SetWindowSize(
clientRect.right,
clientRect.bottom
);
g_renderer->SetMagnifier(g_magnifier);
SetTransform(hwnd);
}
}
void OnMouseDown(HWND hwnd)
{
if (!g_magnifier.visible)
return;
GetCursorPos(&g_dragPos);
ScreenToClient(hwnd, &g_dragPos);
RECT focusRect =
{
g_magnifier.focusPos.x,
g_magnifier.focusPos.y,
g_magnifier.focusPos.x + static_cast<int>(g_magnifier.magnifierSize.cx / g_magnifier.scale),
g_magnifier.focusPos.y + static_cast<int>(g_magnifier.magnifierSize.cy / g_magnifier.scale)
};
if (!PtInRect(&focusRect, g_dragPos))
return;
SetCapture(hwnd);
g_dragging = true;
}
void OnMouseMove(HWND hwnd)
{
if (g_dragging)
{
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
g_magnifier.focusPos.x += pt.x - g_dragPos.x;
g_magnifier.focusPos.y += pt.y - g_dragPos.y;
g_dragPos = pt;
if (g_renderer != NULL)
g_renderer->SetMagnifier(g_magnifier);
InvalidateRect(hwnd, NULL, TRUE);
}
}
void OnMouseUp(HWND hwnd)
{
if (g_dragging)
{
ReleaseCapture();
g_dragging = false;
}
}
void SafeAppend(wchar_t* buffer, size_t bufferSize, size_t* length, wchar_t* text)
{
// Append the new text if it fits into the buffer, and adjust the length.
// Otherwise leave the buffer alone.
size_t c = wcsnlen(text, UINT32_MAX);
if (*length + c < bufferSize)
{
memcpy(buffer + *length, text, c * sizeof(buffer[0]));
*length += c;
buffer[*length] = '\0';
}
}
void SafeAppend(wchar_t* buffer, size_t bufferSize, size_t* length, UINT stringID)
{
*length += LoadString(
g_instance,
stringID,
buffer + *length,
static_cast<int>(bufferSize - *length)
);
}
void SetCaption(HWND hwnd)
{
size_t const bufferSize = 256;
wchar_t caption[bufferSize];
size_t length = 0;
// Load the window title.
SafeAppend(caption, bufferSize, &length, IDS_APP_TITLE);
// Add a hyphen separator.
SafeAppend(caption, bufferSize, &length, L" - ");
// Append a string representing the measuring mode.
switch (g_measuringMode)
{
case DWRITE_MEASURING_MODE_NATURAL:
SafeAppend(caption, bufferSize, &length, IDS_NATURAL_MODE);
break;
case DWRITE_MEASURING_MODE_GDI_CLASSIC:
SafeAppend(caption, bufferSize, &length, IDS_GDI_CLASSIC_MODE);
break;
case DWRITE_MEASURING_MODE_GDI_NATURAL:
SafeAppend(caption, bufferSize, &length, IDS_GDI_NATURAL_MODE);
break;
}
// Add a comma separator.
SafeAppend(caption, bufferSize, &length, L", ");
// Append a string representing the renderer implementation.
switch (g_rendererID)
{
case RendererD2D:
SafeAppend(caption, bufferSize, &length, IDS_USING_D2D);
break;
case RendererDWrite:
SafeAppend(caption, bufferSize, &length, IDS_USING_DWRITE);
break;
}
SetWindowText(hwnd, caption);
}