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

1035 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
//
// Contents: Adapter render target draws using D2D or DirectWrite.
// This demonstrates how to implement your own render target
// for layout drawing callbacks.
//
//----------------------------------------------------------------------------
#include "Common.h"
#include "DrawingEffect.h"
#include "RenderTarget.h"
inline bool operator== (const RenderTargetD2D::ImageCacheEntry& entry, const IWICBitmapSource* original)
{
return entry.original == original;
}
inline bool operator== (const RenderTargetDW::ImageCacheEntry& entry, const IWICBitmapSource* original)
{
return entry.original == original;
}
////////////////////////////////////////////////////////////////////////////////
// Direct2D render target.
HRESULT RenderTargetD2D::Create(ID2D1Factory* d2dFactory, IDWriteFactory* dwriteFactory, HWND hwnd, OUT RenderTarget** renderTarget)
{
*renderTarget = NULL;
HRESULT hr = S_OK;
RenderTargetD2D* newRenderTarget = SafeAcquire(new(std::nothrow) RenderTargetD2D(d2dFactory, dwriteFactory, hwnd));
if (newRenderTarget == NULL)
{
return E_OUTOFMEMORY;
}
hr = newRenderTarget->CreateTarget();
if (FAILED(hr))
SafeRelease(&newRenderTarget);
*renderTarget = SafeDetach(&newRenderTarget);
return hr;
}
RenderTargetD2D::RenderTargetD2D(ID2D1Factory* d2dFactory, IDWriteFactory* dwriteFactory, HWND hwnd)
: hwnd_(hwnd),
hmonitor_(NULL),
d2dFactory_(SafeAcquire(d2dFactory)),
dwriteFactory_(SafeAcquire(dwriteFactory)),
target_(),
brush_()
{
}
RenderTargetD2D::~RenderTargetD2D()
{
SafeRelease(&brush_);
SafeRelease(&target_);
SafeRelease(&d2dFactory_);
SafeRelease(&dwriteFactory_);
}
HRESULT RenderTargetD2D::CreateTarget()
{
// Creates a D2D render target set on the HWND.
HRESULT hr = S_OK;
// Get the window's pixel size.
RECT rect = {};
GetClientRect(hwnd_, &rect);
D2D1_SIZE_U d2dSize = D2D1::SizeU(rect.right, rect.bottom);
// Create a D2D render target.
ID2D1HwndRenderTarget* target = NULL;
hr = d2dFactory_->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd_, d2dSize),
&target
);
if (SUCCEEDED(hr))
{
SafeSet(&target_, target);
// Any scaling will be combined into matrix transforms rather than an
// additional DPI scaling. This simplifies the logic for rendering
// and hit-testing. If an application does not use matrices, then
// using the scaling factor directly is simpler.
target->SetDpi(96.0, 96.0);
// Create a reusable scratch brush, rather than allocating one for
// each new color.
SafeRelease(&brush_);
hr = target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &brush_);
}
if (SUCCEEDED(hr))
{
// Update the initial monitor rendering parameters.
UpdateMonitor();
}
SafeRelease(&target);
return hr;
}
void RenderTargetD2D::Resize(UINT width, UINT height)
{
D2D1_SIZE_U size;
size.width = width;
size.height = height;
target_->Resize(size);
}
void RenderTargetD2D::UpdateMonitor()
{
// Updates rendering parameters according to current monitor.
HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
if (monitor != hmonitor_)
{
// Create based on monitor settings, rather than the defaults of
// gamma=1.8, contrast=.5, and clearTypeLevel=.5
IDWriteRenderingParams* renderingParams = NULL;
dwriteFactory_->CreateMonitorRenderingParams(
monitor,
&renderingParams
);
target_->SetTextRenderingParams(renderingParams);
hmonitor_ = monitor;
InvalidateRect(hwnd_, NULL, FALSE);
SafeRelease(&renderingParams);
}
}
void RenderTargetD2D::BeginDraw()
{
target_->BeginDraw();
target_->SetTransform(D2D1::Matrix3x2F::Identity());
}
void RenderTargetD2D::EndDraw()
{
HRESULT hr = target_->EndDraw();
// If the device is lost for any reason, we need to recreate it.
if (hr == D2DERR_RECREATE_TARGET)
{
// Flush resources and recreate them.
// This is very rare for a device to be lost,
// but it can occur when connecting via Remote Desktop.
imageCache_.clear();
hmonitor_ = NULL;
CreateTarget();
}
}
void RenderTargetD2D::Clear(UINT32 color)
{
target_->Clear(D2D1::ColorF(color));
}
ID2D1Bitmap* RenderTargetD2D::GetCachedImage(IWICBitmapSource* image)
{
// Maps a WIC image source to an aready cached D2D bitmap.
// If not already cached, it creates the D2D bitmap from WIC.
if (image == NULL)
return NULL;
// Find an existing match
std::vector<ImageCacheEntry>::iterator match = std::find(imageCache_.begin(), imageCache_.end(), image);
if (match != imageCache_.end())
return match->converted; // already cached
// Convert the WIC image to a ready-to-use device-dependent D2D bitmap.
// This avoids needing to recreate a new texture every draw call, but
// allows easy reconstruction of textures if the device changes and
// resources need recreation (also lets callers be D2D agnostic).
ID2D1Bitmap* bitmap = NULL;
target_->CreateBitmapFromWicBitmap(image, NULL, &bitmap);
if (bitmap == NULL)
return NULL;
// Save for later calls.
try
{
imageCache_.push_back(ImageCacheEntry(image, bitmap));
}
catch (...)
{
// Out of memory
SafeRelease(&bitmap);
return NULL;
}
// Release it locally and return the pointer.
// The bitmap is now referenced by the bitmap cache.
bitmap->Release();
return bitmap;
}
void RenderTargetD2D::FillRectangle(
const RectF& destRect,
const DrawingEffect& drawingEffect
)
{
ID2D1Brush* brush = GetCachedBrush(&drawingEffect);
if (brush == NULL)
return;
// We will always get a strikethrough as a LTR rectangle
// with the baseline origin snapped.
target_->FillRectangle(destRect, brush);
}
void RenderTargetD2D::DrawImage(
IWICBitmapSource* image,
const RectF& sourceRect, // where in source atlas texture
const RectF& destRect // where on display to draw it
)
{
// Ignore zero size source rects.
// Draw nothing if the destination is zero size.
if (&sourceRect == NULL
|| sourceRect.left >= sourceRect.right
|| sourceRect.top >= sourceRect.bottom
|| destRect.left >= destRect.right
|| destRect.top >= destRect.bottom)
{
return;
}
ID2D1Bitmap* bitmap = GetCachedImage(image);
if (bitmap == NULL)
return;
target_->DrawBitmap(
bitmap,
destRect,
1.0, // opacity
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
sourceRect
);
}
void RenderTargetD2D::DrawTextLayout(
IDWriteTextLayout* textLayout,
const RectF& rect
)
{
if (textLayout == NULL)
return;
Context context(this, NULL);
textLayout->Draw(
&context,
this,
rect.left,
rect.top
);
}
ID2D1Brush* RenderTargetD2D::GetCachedBrush(
const DrawingEffect* effect
)
{
if (effect == NULL || brush_ == NULL)
return NULL;
// Update the D2D brush to the new effect color.
UINT32 bgra = effect->GetColor();
float alpha = (bgra >> 24) / 255.0f;
brush_->SetColor(D2D1::ColorF(bgra, alpha));
return brush_;
}
void RenderTargetD2D::SetTransform(DWRITE_MATRIX const& transform)
{
target_->SetTransform(reinterpret_cast<const D2D1_MATRIX_3X2_F*>(&transform));
}
void RenderTargetD2D::GetTransform(DWRITE_MATRIX& transform)
{
target_->GetTransform(reinterpret_cast<D2D1_MATRIX_3X2_F*>(&transform));
}
void RenderTargetD2D::SetAntialiasing(bool isEnabled)
{
target_->SetAntialiasMode(isEnabled ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE : D2D1_ANTIALIAS_MODE_ALIASED);
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::DrawGlyphRun(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
const DWRITE_GLYPH_RUN* glyphRun,
const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription,
IUnknown* clientDrawingEffect
)
{
// If no drawing effect is applied to run, but a clientDrawingContext
// is passed, use the one from that instead. This is useful for trimming
// signs, where they don't have a color of their own.
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
// Since we use our own custom renderer and explicitly set the effect
// on the layout, we know exactly what the parameter is and can
// safely cast it directly.
DrawingEffect* effect = static_cast<DrawingEffect*>(clientDrawingEffect);
ID2D1Brush* brush = GetCachedBrush(effect);
if (brush == NULL)
return E_FAIL;
target_->DrawGlyphRun(
D2D1::Point2(baselineOriginX, baselineOriginY),
glyphRun,
brush,
measuringMode
);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::DrawUnderline(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
const DWRITE_UNDERLINE* underline,
IUnknown* clientDrawingEffect
)
{
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
DrawingEffect* effect = static_cast<DrawingEffect*>(clientDrawingEffect);
ID2D1Brush* brush = GetCachedBrush(effect);
if (brush == NULL)
return E_FAIL;
// We will always get a strikethrough as a LTR rectangle
// with the baseline origin snapped.
D2D1_RECT_F rectangle =
{
baselineOriginX,
baselineOriginY + underline->offset,
baselineOriginX + underline->width,
baselineOriginY + underline->offset + underline->thickness
};
// Draw this as a rectangle, rather than a line.
target_->FillRectangle(&rectangle, brush);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::DrawStrikethrough(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
const DWRITE_STRIKETHROUGH* strikethrough,
IUnknown* clientDrawingEffect
)
{
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
DrawingEffect* effect = static_cast<DrawingEffect*>(clientDrawingEffect);
ID2D1Brush* brush = GetCachedBrush(effect);
if (brush == NULL)
return E_FAIL;
// We will always get an underline as a LTR rectangle
// with the baseline origin snapped.
D2D1_RECT_F rectangle =
{
baselineOriginX,
baselineOriginY + strikethrough->offset,
baselineOriginX + strikethrough->width,
baselineOriginY + strikethrough->offset + strikethrough->thickness
};
// Draw this as a rectangle, rather than a line.
target_->FillRectangle(&rectangle, brush);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::DrawInlineObject(
void* clientDrawingContext,
FLOAT originX,
FLOAT originY,
IDWriteInlineObject* inlineObject,
BOOL isSideways,
BOOL isRightToLeft,
IUnknown* clientDrawingEffect
)
{
// Inline objects inherit the drawing effect of the text
// they are in, so we should pass it down (if none is set
// on this range, use the drawing context's effect instead).
Context subContext(*reinterpret_cast<RenderTarget::Context*>(clientDrawingContext));
if (clientDrawingEffect != NULL)
subContext.drawingEffect = clientDrawingEffect;
inlineObject->Draw(
&subContext,
this,
originX,
originY,
false,
false,
subContext.drawingEffect
);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::IsPixelSnappingDisabled(
void* clientDrawingContext,
OUT BOOL* isDisabled
)
{
// Enable pixel snapping of the text baselines,
// since we're not animating and don't want blurry text.
*isDisabled = FALSE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::GetCurrentTransform(
void* clientDrawingContext,
OUT DWRITE_MATRIX* transform
)
{
// Simply forward what the real renderer holds onto.
target_->GetTransform(reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform));
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetD2D::GetPixelsPerDip(
void* clientDrawingContext,
OUT FLOAT* pixelsPerDip
)
{
// Any scaling will be combined into matrix transforms rather than an
// additional DPI scaling. This simplifies the logic for rendering
// and hit-testing. If an application does not use matrices, then
// using the scaling factor directly is simpler.
*pixelsPerDip = 1;
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
// DirectWrite render target.
HRESULT RenderTargetDW::Create(IDWriteFactory* dwriteFactory, HWND hwnd, OUT RenderTarget** renderTarget)
{
*renderTarget = NULL;
HRESULT hr = S_OK;
RenderTargetDW* newRenderTarget = SafeAcquire(new(std::nothrow) RenderTargetDW(dwriteFactory, hwnd));
if (newRenderTarget == NULL)
{
return E_OUTOFMEMORY;
}
hr = newRenderTarget->Initialize();
if (FAILED(hr))
SafeRelease(&newRenderTarget);
*renderTarget = SafeDetach(&newRenderTarget);
return hr;
}
RenderTargetDW::RenderTargetDW(IDWriteFactory* dwriteFactory, HWND hwnd)
: hwnd_(hwnd),
hmonitor_(NULL),
dwriteFactory_(SafeAcquire(dwriteFactory)),
gdiInterop_(),
renderingParams_(),
target_()
{
}
HRESULT RenderTargetDW::Initialize()
{
// Creates a DirectWrite bitmap render target.
HRESULT hr = S_OK;
hr = dwriteFactory_->GetGdiInterop(&gdiInterop_);
if (FAILED(hr))
return hr;
RECT rect = {};
HDC hdc = GetDC(hwnd_);
GetClientRect(hwnd_, &rect);
hr = gdiInterop_->CreateBitmapRenderTarget(hdc, rect.right, rect.bottom, &target_);
ReleaseDC(hwnd_, hdc);
if (FAILED(hr))
return hr;
// Any scaling will be combined into matrix transforms rather than an
// additional DPI scaling. This simplifies the logic for rendering
// and hit-testing. If an application does not use matrices, then
// using the scaling factor directly is simpler.
target_->SetPixelsPerDip(1);
// Update the initial monitor rendering parameters.
UpdateMonitor();
if (renderingParams_ == NULL)
return E_FAIL;
return hr;
}
RenderTargetDW::~RenderTargetDW()
{
SafeRelease(&renderingParams_);
SafeRelease(&gdiInterop_);
SafeRelease(&target_);
SafeRelease(&dwriteFactory_);
for (size_t i = 0, ci = imageCache_.size(); i < ci; ++i)
{
DeleteObject(imageCache_[i].converted);
}
}
void RenderTargetDW::Resize(UINT width, UINT height)
{
target_->Resize(width, height);
}
void RenderTargetDW::UpdateMonitor()
{
// Updates rendering parameters according to current monitor.
HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
if (monitor != hmonitor_)
{
// Create based on monitor settings, rather than the defaults of
// gamma=1.8, contrast=.5, and clearTypeLevel=.5
SafeRelease(&renderingParams_);
dwriteFactory_->CreateMonitorRenderingParams(
monitor,
&renderingParams_
);
hmonitor_ = monitor;
InvalidateRect(hwnd_, NULL, FALSE);
}
}
void RenderTargetDW::BeginDraw()
{
memoryHdc_ = target_->GetMemoryDC();
// Explicitly disable mirroring of content, otherwise
// elements may be drawn reversed.
SetLayout(memoryHdc_, LAYOUT_BITMAPORIENTATIONPRESERVED);
SetGraphicsMode(memoryHdc_, GM_ADVANCED);
}
void RenderTargetDW::EndDraw()
{
SIZE size;
target_->GetSize(&size);
HDC hdc = GetDC(hwnd_);
SetGraphicsMode(memoryHdc_, GM_COMPATIBLE);
// Transfer from DWrite's rendering target to the actual display
// Also, explicitly disable mirroring of bitmaps, otherwise the
// text can be literally drawn backwards, and ClearType is incorrect.
// Note that we set a raster operation rather than calling SetLayout
// here.
BitBlt(
hdc,
0, 0,
size.cx, size.cy,
memoryHdc_,
0, 0,
SRCCOPY | NOMIRRORBITMAP
);
ReleaseDC(hwnd_, hdc);
}
void RenderTargetDW::Clear(UINT32 color)
{
SIZE size;
target_->GetSize(&size);
SetDCBrushColor(memoryHdc_, color);
SelectObject(memoryHdc_, GetStockObject(NULL_PEN));
SelectObject(memoryHdc_, GetStockObject(DC_BRUSH));
Rectangle(memoryHdc_, 0,0, size.cx + 1, size.cy + 1);
}
HBITMAP RenderTargetDW::GetCachedImage(IWICBitmapSource* image)
{
// Maps a WIC image source to an aready cached HBITMAP.
// If not already cached, it creates the HBITMAP from WIC.
if (image == NULL)
return NULL;
// Find an existing cached match for this image.
std::vector<ImageCacheEntry>::iterator match = std::find(imageCache_.begin(), imageCache_.end(), image);
if (match != imageCache_.end())
return match->converted; // already cached
// Convert the WIC image to a ready-to-use device-dependent GDI bitmap.
// so that we don't recreate a new bitmap every draw call.
//
// * Note this expects BGRA pixel format.
UINT width, height;
if (FAILED(image->GetSize(&width, &height)))
return NULL;
UINT stride = width * 4;
UINT pixelBufferSize = stride * height;
std::vector<UINT8> pixelBuffer(pixelBufferSize);
if (FAILED(image->CopyPixels(NULL, stride, pixelBufferSize, &pixelBuffer[0])))
return NULL;
// Initialize bitmap information.
BITMAPINFO bmi = {
sizeof(BITMAPINFOHEADER), // biSize
width, // biWidth
-int(height), // biHeight
1, // biPlanes
32, // biBitCount
BI_RGB, // biCompression
pixelBufferSize, // biSizeImage
1, // biXPelsPerMeter
1, // biYPelsPerMeter
0, // biClrUsed
0, // biClrImportant
0 // RGB QUAD
};
HBITMAP bitmap = CreateCompatibleBitmap(memoryHdc_, width, height);
if (bitmap == NULL)
return NULL;
SetDIBits(
memoryHdc_,
bitmap,
0, // starting line
height, // total scanlines
&pixelBuffer[0],
&bmi,
DIB_RGB_COLORS
);
// Save for later calls.
try
{
imageCache_.push_back(ImageCacheEntry(image, bitmap));
}
catch (...)
{
// Out of memory
DeleteObject(bitmap);
return NULL;
}
return bitmap;
}
void RenderTargetDW::FillRectangle(
const RectF& destRect,
const DrawingEffect& drawingEffect
)
{
// Convert D2D/GDI+ color order to GDI's COLORREF,
// which expects the lowest byte to be red.
// The alpha channel must also be cleared.
// GDI's Rectangle() does not support translucency,
// so draw nothing. Alternately, you could draw a hash.
if (drawingEffect.GetColor() < 0xE0000000)
return;
COLORREF gdiColor = drawingEffect.GetColorRef();
SetDCBrushColor(memoryHdc_, gdiColor);
SelectObject(memoryHdc_, GetStockObject(NULL_PEN));
SelectObject(memoryHdc_, GetStockObject(DC_BRUSH));
Rectangle(
memoryHdc_,
int(floor(destRect.left + .5f)),
int(floor(destRect.top + .5f)),
int(floor(destRect.right + .5f)),
int(floor(destRect.bottom + .5f))
);
}
void RenderTargetDW::DrawImage(
IWICBitmapSource* image,
const RectF& sourceRect, // where in source atlas texture
const RectF& destRect // where on display to draw it
)
{
// Ignore zero size source rects.
// Draw nothing if the destination is zero size.
if (&sourceRect == NULL
|| sourceRect.left >= sourceRect.right
|| sourceRect.top >= sourceRect.bottom
|| destRect.left >= destRect.right
|| destRect.top >= destRect.bottom)
{
return;
}
HBITMAP bitmap = GetCachedImage(image);
if (bitmap == NULL)
return;
HDC tempHdc = CreateCompatibleDC(memoryHdc_);
HBITMAP oldBitmap = (HBITMAP)SelectObject(tempHdc, bitmap);
const static BLENDFUNCTION blend = {
AC_SRC_OVER, // blend-op
0, // flags
255, // alpha
AC_SRC_ALPHA
};
// Notice that since GDI doesn't accept floating point coordinates,
// we round them to the nearest integer. This helps align the image's
// baseline to that of nearby text, since round-nearest is the
// same method generally used by D2D and DirectWrite internally.
AlphaBlend(
memoryHdc_,
int(floor(destRect.left + .5f)),
int(floor(destRect.top + .5f)),
int(floor(destRect.right - destRect.left + .5f)),
int(floor(destRect.bottom - destRect.top + .5f)),
tempHdc,
int(sourceRect.left),
int(sourceRect.top),
int(sourceRect.right - sourceRect.left),
int(sourceRect.bottom - sourceRect.top),
blend
);
SelectObject(tempHdc, oldBitmap);
DeleteDC(tempHdc);
}
void RenderTargetDW::DrawTextLayout(
IDWriteTextLayout* textLayout,
const RectF& rect
)
{
if (textLayout == NULL)
return;
Context context(this, NULL);
textLayout->Draw(
&context,
this,
rect.left,
rect.top
);
}
void RenderTargetDW::SetTransform(DWRITE_MATRIX const& transform)
{
target_->SetCurrentTransform(&transform);
SetGraphicsMode(memoryHdc_, GM_ADVANCED);
SetWorldTransform(memoryHdc_, (XFORM*)&transform);
}
void RenderTargetDW::GetTransform(DWRITE_MATRIX& transform)
{
target_->GetCurrentTransform(&transform);
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::DrawGlyphRun(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
const DWRITE_GLYPH_RUN* glyphRun,
const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription,
IUnknown* clientDrawingEffect
)
{
// If no drawing effect is applied to run, but a clientDrawingContext
// is passed, use the one from that instead. This is useful for trimming
// signs, where they don't have a color of their own.
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
// Since we use our own custom renderer and explicitly set the effect
// on the layout, we know exactly what the parameter is and can
// safely cast it directly.
DrawingEffect* effect = static_cast<DrawingEffect*>(clientDrawingEffect);
if (effect == NULL)
return E_FAIL;
// Pass on the drawing call to the render target to do the real work.
target_->DrawGlyphRun(
baselineOriginX,
baselineOriginY,
measuringMode,
glyphRun,
renderingParams_,
effect->GetColorRef(),
NULL
);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::DrawUnderline(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
const DWRITE_UNDERLINE* underline,
IUnknown* clientDrawingEffect
)
{
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
return DrawLine(
baselineOriginX,
baselineOriginY,
underline->width,
underline->offset,
underline->thickness,
clientDrawingEffect
);
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::DrawStrikethrough(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
const DWRITE_STRIKETHROUGH* strikethrough,
IUnknown* clientDrawingEffect
)
{
clientDrawingEffect = GetDrawingEffect(clientDrawingContext, clientDrawingEffect);
return DrawLine(
baselineOriginX,
baselineOriginY,
strikethrough->width,
strikethrough->offset,
strikethrough->thickness,
clientDrawingEffect
);
}
HRESULT RenderTargetDW::DrawLine(
float baselineOriginX,
float baselineOriginY,
float width,
float offset,
float thickness,
IUnknown* clientDrawingEffect
)
{
// Get solid color from drawing effect.
DrawingEffect* effect = static_cast<DrawingEffect*>(clientDrawingEffect);
if (effect == NULL)
return E_FAIL;
HDC hdc = target_->GetMemoryDC();
RECT rect = {
LONG(baselineOriginX),
LONG(baselineOriginY + offset),
LONG(baselineOriginX + width),
LONG(baselineOriginY + offset + thickness),
};
// Account for the possibility that the line became zero height,
// which can occur at small font sizes.
if (rect.bottom == rect.top)
rect.bottom++;
// Draw the line
// Note that GDI wants RGB, not BGRA like D2D.
SetBkColor(hdc, effect->GetColorRef());
ExtTextOut(
hdc,
0, 0,
ETO_OPAQUE,
&rect,
L"",
0,
NULL
);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::DrawInlineObject(
void* clientDrawingContext,
FLOAT originX,
FLOAT originY,
IDWriteInlineObject* inlineObject,
BOOL isSideways,
BOOL isRightToLeft,
IUnknown* clientDrawingEffect
)
{
// Inline objects inherit the drawing effect of the text
// they are in, so we should pass it down (if none is set
// on this range, use the drawing context's effect instead).
Context subContext(*reinterpret_cast<RenderTarget::Context*>(clientDrawingContext));
if (clientDrawingEffect != NULL)
subContext.drawingEffect = clientDrawingEffect;
inlineObject->Draw(
&subContext,
this,
originX,
originY,
false,
false,
subContext.drawingEffect
);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::IsPixelSnappingDisabled(
void* clientDrawingContext,
OUT BOOL* isDisabled
)
{
// Enable pixel snapping of the text baselines,
// since we're not animating and don't want blurry text.
*isDisabled = FALSE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::GetCurrentTransform(
void* clientDrawingContext,
OUT DWRITE_MATRIX* transform
)
{
// Simply forward what the real renderer holds onto.
target_->GetCurrentTransform(transform);
return S_OK;
}
HRESULT STDMETHODCALLTYPE RenderTargetDW::GetPixelsPerDip(
void* clientDrawingContext,
OUT FLOAT* pixelsPerDip
)
{
// Simply forward what the real renderer holds onto.
*pixelsPerDip = target_->GetPixelsPerDip();
return S_OK;
}