這篇筆記是紀錄用 DirectWrite 在遊戲或一般 application UI 中寫文字會用到的東西。
IDWriteFactory
首先,所有 DirectWrite 的物件都建立在 IDWriteFactory 上。
IDWriteFactory* dw_factory_;
HRESULT hret = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&dw_factory_)
);
IDWriteTextFormat
定義了單一文字的字型、大小、Font Weight (筆觸厚重)、Font Style (斜體、義大利斜體)、Font Stretch (左右的拉長壓縮)。也可以定義一段文字要靠左靠右靠中(SetTextAlignment),與段落跟邊界的空間 (SetParagraphAlignment)。
但是文字資料,文字要畫的位置都不在這裏面。
IDWriteTextFormat* dw_textformat_;
HRESULT hret = dw_factory_->CreateTextFormat(
L"Reem Kufi Regular", // font name
NULL, // IDWriteFontCollection*, NULL for system font collection
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
50, // font size in dip (1/96 inch)
L"", //locale name
&dw_textformat_
);
hret = dw_textformat_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
hret = dw_textformat_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
IDWriteTextLayout
定義了要繪製的文字字串,與繪製時邊框的長寬。建立 layout 時,除了需要 factory,也需要 text format。
static const WCHAR sc_str[] = L"This is the text I want to draw!";
HRESULT hret = dw_factory_->CreateTextLayout(
sc_str, // The string to be laid out and formatted.
ARRAYSIZE(sc_str)-1, // The length of the string.
dw_textformat_, // The text format to apply to the string (contains font information, etc).
300, // The width of the layout box.
50, // The height of the layout box.
&dw_textlayout_ // The IDWriteTextLayout interface pointer.
);
關於 layout width and height,它會自動換行,但不保證一定會畫在框框內。可以在繪製時,要求超出 layout 的不要繪製,但是這麼簡單的功能在製作可以 scrolling 的文字框時會無法實現。需要用 IDWriteTextRenderer callback 來自行繪製。
還有做排版時一定會需要 “事先” (pre-render) 知道文字區塊繪製後的真實長寬,下面是取得 TextLayout 的方法。
DWRITE_TEXT_METRICS textMetrics;
hr = dw_textlayout_->GetMetrics(&textMetrics);
ID2D1RenderTarget::DrawText()、只需要 text format 的文字繪製
最簡單的繪製方法,只需要 IDWriteTextFormat。因為 text format 沒有文字和位置,所以參數要提供字串與繪製的範圍 (rect),也需要額外提供 Brush (有四種,Solid/Linear Gradient/Circular Gradient/Bitmap),與如果畫到邊界之外時的處裡方法的 option。
ID2D1RenderTarget::DrawTextLayout()、繪製 text layout 的 API
不同於 text format,text layout 已經有必要的資料了,所以 DrawTextLayout() 只需要額外提供 Brush 和超出邊界處裡的 option。
IDWriteTextLayout::Draw()、提供 callback 自行處理文字的繪製
上面的 API 都是自動完成繪製的,而使用 layout 的 Draw() API 是利用 callback,在繪製過程提供文字的 Geometry (由很多線組成的文字外框)、文字座標、和其他很專業才會用的東西 (ex: effect object),讓你自己繪製文字邊框和填色。
Callback interface 是 IDWriteTextRenderer,Microsoft 有個 Sample Implementaion, named CustomTextRender 可以在 github 找到。我這裡修改了繪製最主要的 CustomTextRenderer::DrawGlyphRun() 讓繪製時不超出外框 (pCanvas) 當範例,因為這也是最常用到的功能,另一個常用的是輸出限制在一行,不過那只要看 baselineOriginY 忽略掉 Y 值太高的 function call 即可。
IFACEMETHODIMP CustomTextRenderer::DrawGlyphRun(
__maybenull void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
__in DWRITE_GLYPH_RUN const* glyphRun,
__in DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
IUnknown* clientDrawingEffect
)
{
// Create a path geometry to represent the area text should be draw,
// actually you can create a rect geometry and any other geometry instead
ID2D1PathGeometry* pCanvas = NULL;
hr = pD2DFactory_->CreatePathGeometry(&pCanvas);
pCanvas->Open(&pSink);
pSink->SetFillMode(D2D1_FILL_MODE_WINDING);
pSink->BeginFigure(
D2D1::Point2F(30, 30),
D2D1_FIGURE_BEGIN_FILLED
);
D2D1_POINT_2F points[5] = {
D2D1::Point2F(30, 30),
D2D1::Point2F(100, 30),
D2D1::Point2F(100, 100),
D2D1::Point2F(30, 100),
D2D1::Point2F(30, 30)
};
pSink->AddLines(points, ARRAYSIZE(points));
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
pSink->Close();
SafeRelease(&pSink);
//
//
// ... skip ...
//
//
// Combine (intersect) final path geometric, which is
// pTransformedGeometry, with pCanvas
ID2D1PathGeometry* pPathCombine = NULL;
hr = pD2DFactory_->CreatePathGeometry(
&pPathCombine
);
pPathCombine->Open(&pSink);
pTransformedGeometry->CombineWithGeometry(
pCanvas,
D2D1_COMBINE_MODE_INTERSECT,
NULL,
NULL,
pSink);
pSink->Close();
SafeRelease(&pSink);
// Draw the outline of the glyph run
pRT_->DrawGeometry(
pPathCombine,
pOutlineBrush_
);
//
// ... skip ...
//
}
Check the Direct2D Geometry section to understand how Geometry works.