632 lines
17 KiB
C++
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;
|
|
}
|