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

632 lines
17 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 "TextHelpers.h"
#include "DWriteRenderer.h"
namespace
{
DWRITE_MATRIX const g_identityTransform =
{
1, 0,
0, 1,
0, 0
};
}
IRenderer* CreateDWriteRenderer(
HWND hwnd,
UINT width,
UINT height,
IDWriteTextFormat* textFormat,
wchar_t const* text
)
{
return new(std::nothrow) DWriteRenderer(
hwnd,
width,
height,
textFormat,
text
);
}
DWriteRenderer::DWriteRenderer(
HWND hwnd,
UINT width,
UINT height,
IDWriteTextFormat* textFormat,
wchar_t const* text
) :
hwnd_(hwnd),
width_(width),
height_(height),
measuringMode_(DWRITE_MEASURING_MODE_NATURAL),
transform_(g_identityTransform),
text_(text),
borderPen_(NULL),
textFormat_(SafeAcquire(textFormat)),
textLayout_(),
renderTarget_(),
magnifierTarget_(),
renderingParams_()
{
magnifier_.visible = false;
}
void DWriteRenderer::SetFormat(IDWriteTextFormat* format)
{
SafeSet(&textFormat_, format);
FreeTextLayout();
}
void DWriteRenderer::SetText(wchar_t const* text)
{
text_ = text;
FreeTextLayout();
}
void DWriteRenderer::SetMeasuringMode(DWRITE_MEASURING_MODE measuringMode)
{
measuringMode_ = measuringMode;
FreeTextLayout();
}
void DWriteRenderer::SetTransform(DWRITE_MATRIX const& transform)
{
transform_ = transform;
// GDI-compatible layouts depend on the DPI and transform.
if (measuringMode_ != DWRITE_MEASURING_MODE_NATURAL)
FreeTextLayout();
}
void DWriteRenderer::SetMagnifier(MagnifierInfo const& magnifier)
{
magnifier_ = magnifier;
SafeRelease(&magnifierTarget_);
}
void DWriteRenderer::SetWindowSize(UINT width, UINT height)
{
width_ = width;
height_ = height;
SafeRelease(&renderTarget_);
UpdateTextOrigin();
}
void DWriteRenderer::SetMonitor(HMONITOR monitor)
{
g_dwriteFactory->CreateMonitorRenderingParams(monitor, &renderingParams_);
}
HRESULT DWriteRenderer::Draw(HDC hdc)
{
HRESULT hr = S_OK;
// Create the bitmap render target if we don't already have it.
if (renderTarget_ == NULL)
{
IDWriteGdiInterop* gdiInterop = NULL;
if (SUCCEEDED(hr))
{
hr = g_dwriteFactory->GetGdiInterop(&gdiInterop);
}
if (SUCCEEDED(hr))
{
hr = gdiInterop->CreateBitmapRenderTarget(hdc, width_, height_, &renderTarget_);
}
if (SUCCEEDED(hr))
{
hr = renderTarget_->SetPixelsPerDip(g_dpiY / 96.0f);
}
SafeRelease(&gdiInterop);
}
// Create the rendering params object if we haven't already.
if (SUCCEEDED(hr) && renderingParams_ == NULL)
{
hr = g_dwriteFactory->CreateRenderingParams(&renderingParams_);
}
HDC hdcMem = NULL;
if (SUCCEEDED(hr))
{
// Clear the background.
hdcMem = renderTarget_->GetMemoryDC();
SelectObject(hdcMem, GetSysColorBrush(COLOR_WINDOW));
PatBlt(hdcMem, 0, 0, width_, height_, PATCOPY);
// Set the rendering transform.
renderTarget_->SetCurrentTransform(&transform_);
// Prepare the render target we use for the magnifier.
PrepareMagnifier(hdc);
}
if (SUCCEEDED(hr))
{
hr = InitializeTextLayout();
}
if (SUCCEEDED(hr))
{
// Render the text. The Draw method will call back to the IDWriteTextRenderer
// methods implemented by this class.
hr = textLayout_->Draw(
NULL, // optional client drawing context
this, // renderer callback
textOriginX_,
textOriginY_
);
}
if (SUCCEEDED(hr))
{
DrawMagnifier();
// Do the final BitBlt to the specified HDC.
BitBlt(hdc, 0, 0, width_, height_, hdcMem, 0, 0, SRCCOPY | NOMIRRORBITMAP);
}
return hr;
}
HRESULT DWriteRenderer::PrepareMagnifier(HDC hdc)
{
HRESULT hr = S_OK;
if (!magnifier_.visible)
{
SafeRelease(&magnifierTarget_);
return hr;
}
// Determine the size and scale factor for the magnifier render target. In vector
// mode we render using a scale transform. In all other modes we render at normal
// size and then scale up the pixels afterwards.
SIZE targetSize = magnifier_.magnifierSize;
int targetScale = magnifier_.scale;
if (magnifier_.type != MagnifierInfo::Vector)
{
targetSize.cx /= targetScale;
targetSize.cy /= targetScale;
targetScale = 1;
}
// Create a separate render target for the magnifier if we haven't already.
if (SUCCEEDED(hr) && magnifierTarget_ == NULL)
{
IDWriteGdiInterop* gdiInterop = NULL;
if (SUCCEEDED(hr))
{
hr = g_dwriteFactory->GetGdiInterop(&gdiInterop);
}
if (SUCCEEDED(hr))
{
hr = gdiInterop->CreateBitmapRenderTarget(hdc, targetSize.cx, targetSize.cy, &magnifierTarget_);
}
SafeRelease(&gdiInterop);
}
DWRITE_MATRIX zoomTransform;;
if (SUCCEEDED(hr))
{
// Clear the background.
HDC hdcMagnifier = magnifierTarget_->GetMemoryDC();
SelectObject(hdcMagnifier, GetSysColorBrush(COLOR_WINDOW));
PatBlt(hdcMagnifier, 0, 0, magnifier_.magnifierSize.cx, magnifier_.magnifierSize.cy, PATCOPY);
// Create a transform that translates and scales the focus rect to the origin of the magnifier target.
float focusLeft = PixelsToDipsX(magnifier_.focusPos.x);
float focusTop = PixelsToDipsY(magnifier_.focusPos.y);
zoomTransform.m11 = transform_.m11 * targetScale;
zoomTransform.m12 = transform_.m12 * targetScale;
zoomTransform.m21 = transform_.m21 * targetScale;
zoomTransform.m22 = transform_.m22 * targetScale;
zoomTransform.dx = (transform_.dx - focusLeft) * targetScale;
zoomTransform.dy = (transform_.dy - focusTop) * targetScale;
}
if (SUCCEEDED(hr))
{
hr = magnifierTarget_->SetCurrentTransform(&zoomTransform);
}
return hr;
}
void DWriteRenderer::DrawMagnifier()
{
if (magnifierTarget_ == NULL)
return;
HDC memoryDC = renderTarget_->GetMemoryDC();
// Copy the text from the magnifier render target to the main render target.
switch (magnifier_.type)
{
case MagnifierInfo::Vector:
// We rendered the text at the larger scale; just copy it.
BitBlt(
memoryDC,
magnifier_.magnifierPos.x,
magnifier_.magnifierPos.y,
magnifier_.magnifierSize.cx,
magnifier_.magnifierSize.cy,
magnifierTarget_->GetMemoryDC(),
0,
0,
SRCCOPY | NOMIRRORBITMAP
);
break;
case MagnifierInfo::Pixel:
// We rendered the text at normal size; copy and scale up.
StretchBlt(
memoryDC,
magnifier_.magnifierPos.x,
magnifier_.magnifierPos.y,
magnifier_.magnifierSize.cx,
magnifier_.magnifierSize.cy,
magnifierTarget_->GetMemoryDC(),
0,
0,
magnifier_.magnifierSize.cx / magnifier_.scale,
magnifier_.magnifierSize.cy / magnifier_.scale,
SRCCOPY | NOMIRRORBITMAP
);
break;
case MagnifierInfo::Subpixel:
SubpixelZoom();
break;
}
// Draw the borders around the magnifier and focus rectangle.
if (borderPen_ == NULL)
{
borderPen_ = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
}
HGDIOBJ oldBrush = SelectObject(memoryDC, GetStockObject(NULL_BRUSH));
HGDIOBJ oldPen = SelectObject(memoryDC, borderPen_);
Rectangle(
memoryDC,
magnifier_.magnifierPos.x,
magnifier_.magnifierPos.y,
magnifier_.magnifierPos.x + magnifier_.magnifierSize.cx,
magnifier_.magnifierPos.y + magnifier_.magnifierSize.cy
);
Rectangle(
memoryDC,
magnifier_.focusPos.x,
magnifier_.focusPos.y,
magnifier_.focusPos.x + magnifier_.magnifierSize.cx / magnifier_.scale,
magnifier_.focusPos.y + magnifier_.magnifierSize.cy / magnifier_.scale
);
SelectObject(memoryDC, oldPen);
SelectObject(memoryDC, oldBrush);
}
void DWriteRenderer::SubpixelZoom()
{
bool bgr = renderingParams_->GetPixelGeometry() == DWRITE_PIXEL_GEOMETRY_BGR;
// Get the DIB selection selected into each IDWriteBitmapRenderTarget's memory DC.
DIBSECTION srcDib;
if (GetObject(GetCurrentObject(magnifierTarget_->GetMemoryDC(), OBJ_BITMAP), sizeof(srcDib), &srcDib) != sizeof(srcDib))
return;
DIBSECTION dstDib;
if (GetObject(GetCurrentObject(renderTarget_->GetMemoryDC(), OBJ_BITMAP), sizeof(dstDib), &dstDib) != sizeof(dstDib))
return;
// Point to the pixels. Each DIB section is a 32-bit per pixel top-down DIB.
int const srcWidth = srcDib.dsBm.bmWidth;
int const srcHeight = srcDib.dsBm.bmHeight;
UINT32 const* const srcBits = static_cast<UINT32*>(srcDib.dsBm.bmBits);
int const dstWidth = dstDib.dsBm.bmWidth;
int const dstHeight = dstDib.dsBm.bmHeight;
UINT32* const dstBits = static_cast<UINT32*>(dstDib.dsBm.bmBits);
// Number of target pixels per source pixel and source subpixel.
int const scale = magnifier_.scale;
int const subpixelScale = scale / 3;
int const pixelGap = scale % 3;
// Mask of colors for left, center, and right subpixels.
UINT32 const maskL = bgr ? 0x0000FF : 0xFF0000;
UINT32 const maskC = 0x00FF00;
UINT32 const maskR = bgr ? 0xFF0000 : 0x0000FF;
// Iterate over all the source scan lines.
for (int y = 0; y < srcHeight; ++y)
{
UINT32 const* srcRow = srcBits + (y * srcWidth);
// Determine the corresponding range of Y values in the destination bitmap.
int minDstY = (y * scale) + magnifier_.magnifierPos.y;
int limDstY = minDstY + scale;
// Consrain the destination Y values to fit in the destination bitmap.
if (minDstY < 0)
minDstY = 0;
if (limDstY > dstHeight)
limDstY = dstHeight;
// Are any of the destination scan lines visible?
if (minDstY < limDstY)
{
UINT32* const firstDstRow = dstBits + (minDstY * dstWidth);
int dstX = magnifier_.magnifierPos.x;
// Iterate over all the pixels in the source scan line.
for (int x = 0; x < srcWidth; ++x)
{
UINT32 const color = srcRow[x];
// Fill in the destination pixels for the left, center,
// and right color stripes.
for (int i = 0; i < subpixelScale; ++i, ++dstX)
{
if (dstX >= 0 && dstX < dstWidth)
firstDstRow[dstX] = color & maskL;
}
for (int i = 0; i < subpixelScale; ++i, ++dstX)
{
if (dstX >= 0 && dstX < dstWidth)
firstDstRow[dstX] = color & maskC;
}
for (int i = 0; i < subpixelScale; ++i, ++dstX)
{
if (dstX >= 0 && dstX < dstWidth)
firstDstRow[dstX] = color & maskR;
}
// If the scale is not a multiple if three, we'll have a black
// gap between the pixels.
for (int i = 0; i < pixelGap; ++i, ++dstX)
{
if (dstX >= 0 && dstX < dstWidth)
firstDstRow[dstX] = 0;
}
}
// Copy the destination row we just initialized to the remaining
// destination rows for this scan line.
UINT32* dstRow = firstDstRow + dstWidth;
for (int y2 = minDstY + 1; y2 < limDstY; ++y2, dstRow += dstWidth)
{
memcpy(
dstRow + magnifier_.magnifierPos.x,
firstDstRow + magnifier_.magnifierPos.x,
(dstX - magnifier_.magnifierPos.x) * sizeof(UINT32)
);
}
}
}
}
HRESULT DWriteRenderer::InitializeTextLayout()
{
HRESULT hr = S_OK;
if (textLayout_ == NULL)
{
if (measuringMode_ == DWRITE_MEASURING_MODE_NATURAL)
{
hr = g_dwriteFactory->CreateTextLayout(
text_,
lstrlenW(text_),
textFormat_,
g_formatWidth,
0, // max height
&textLayout_
);
}
else
{
BOOL useGdiNatural = (measuringMode_ == DWRITE_MEASURING_MODE_GDI_NATURAL);
hr = g_dwriteFactory->CreateGdiCompatibleTextLayout(
text_,
lstrlenW(text_),
textFormat_,
g_formatWidth,
0, // max height
g_dpiY / 96.0f, // pixels per DIP
&transform_,
useGdiNatural,
&textLayout_
);
}
if (SUCCEEDED(hr))
{
hr = UpdateTextOrigin();
}
}
return hr;
}
HRESULT DWriteRenderer::UpdateTextOrigin()
{
HRESULT hr = S_OK;
if (textLayout_ != NULL)
{
// Get the text layout size.
DWRITE_TEXT_METRICS metrics = {};
hr = textLayout_->GetMetrics(&metrics);
// Center the text.
textOriginX_ = (PixelsToDipsX(width_) - metrics.width) * 0.5f;
textOriginY_ = (PixelsToDipsY(height_) - metrics.height) * 0.5f;
}
return hr;
}
void DWriteRenderer::FreeTextLayout()
{
SafeRelease(&textLayout_);
}
//
// IUnknown methods
//
// These methods are never called in this scenario so we just use stub
// implementations.
//
HRESULT STDMETHODCALLTYPE DWriteRenderer::QueryInterface(
REFIID riid,
void** ppvObject
)
{
*ppvObject = NULL;
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE DWriteRenderer::AddRef()
{
return 0;
}
ULONG STDMETHODCALLTYPE DWriteRenderer::Release()
{
return 0;
}
//
// IDWritePixelSnapping::IsPixelSnappingDisabled
//
HRESULT STDMETHODCALLTYPE DWriteRenderer::IsPixelSnappingDisabled(
void* clientDrawingContext,
OUT BOOL* isDisabled
)
{
*isDisabled = FALSE;
return S_OK;
}
//
// IDWritePixelSnapping::GetCurrentTransform
//
HRESULT STDMETHODCALLTYPE DWriteRenderer::GetCurrentTransform(
void* clientDrawingContext,
OUT DWRITE_MATRIX* transform
)
{
*transform = transform_;
return S_OK;
}
//
// IDWritePixelSnapping::GetPixelsPerDip
//
HRESULT STDMETHODCALLTYPE DWriteRenderer::GetPixelsPerDip(
void* clientDrawingContext,
OUT FLOAT* pixelsPerDip
)
{
*pixelsPerDip = g_dpiY / 96.0f;
return S_OK;
}
HRESULT STDMETHODCALLTYPE DWriteRenderer::DrawGlyphRun(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
DWRITE_GLYPH_RUN const* glyphRun,
DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
IUnknown* clientDrawingEffect
)
{
HRESULT hr = S_OK;
hr = renderTarget_->DrawGlyphRun(
baselineOriginX,
baselineOriginY,
measuringMode,
glyphRun,
renderingParams_,
GetSysColor(COLOR_WINDOWTEXT)
);
if (SUCCEEDED(hr) && magnifierTarget_ != NULL)
{
hr = magnifierTarget_->DrawGlyphRun(
baselineOriginX,
baselineOriginY,
measuringMode,
glyphRun,
renderingParams_,
GetSysColor(COLOR_WINDOWTEXT)
);
}
return hr;
}
HRESULT STDMETHODCALLTYPE DWriteRenderer::DrawUnderline(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_UNDERLINE const* underline,
IUnknown* clientDrawingEffect
)
{
// We don't use underline in this application.
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE DWriteRenderer::DrawStrikethrough(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_STRIKETHROUGH const* strikethrough,
IUnknown* clientDrawingEffect
)
{
// We don't use strikethrough in this application.
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE DWriteRenderer::DrawInlineObject(
void* clientDrawingContext,
FLOAT originX,
FLOAT originY,
IDWriteInlineObject* inlineObject,
BOOL isSideways,
BOOL isRightToLeft,
IUnknown* clientDrawingEffect
)
{
// We don't use inline objects in this application.
return E_NOTIMPL;
}