#include "uipriv_windows.hpp"
#include "draw.hpp"
struct uiDrawTextLayout {
IDWriteTextFormat *format;
IDWriteTextLayout *layout;
std::vector<backgroundFunc> *backgroundFuncs;
UINT32 nLines;
struct lineInfo *lineInfo;
size_t *u8tou16;
size_t nUTF8;
size_t *u16tou8;
size_t nUTF16;
};
#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))
struct lineInfo {
size_t startPos; size_t endPos;
size_t newlineCount;
double x;
double y;
double width;
double height;
double baseline;
};
static void computeLineInfo(uiDrawTextLayout *tl)
{
DWRITE_LINE_METRICS *dlm;
size_t nextStart;
UINT32 i, j;
DWRITE_HIT_TEST_METRICS *htm;
UINT32 nFragments, unused;
HRESULT hr;
hr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines));
if (hr == S_OK) {
} else if (hr != E_NOT_SUFFICIENT_BUFFER)
logHRESULT(L"error getting number of lines in IDWriteTextLayout", hr);
tl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), "struct lineInfo[] (text layout)");
dlm = new DWRITE_LINE_METRICS[tl->nLines];
hr = tl->layout->GetLineMetrics(dlm, tl->nLines, &unused);
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout line metrics", hr);
nextStart = 0;
for (i = 0; i < tl->nLines; i++) {
tl->lineInfo[i].startPos = nextStart;
tl->lineInfo[i].endPos = nextStart + dlm[i].length;
tl->lineInfo[i].newlineCount = dlm[i].newlineLength;
nextStart = tl->lineInfo[i].endPos;
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
0, 0,
NULL, 0, &nFragments);
if (hr != S_OK && hr != E_NOT_SUFFICIENT_BUFFER)
logHRESULT(L"error getting IDWriteTextLayout line fragment count", hr);
htm = new DWRITE_HIT_TEST_METRICS[nFragments];
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
0, 0,
htm, nFragments, &unused);
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout line fragment metrics", hr);
tl->lineInfo[i].x = htm[0].left;
tl->lineInfo[i].y = htm[0].top;
tl->lineInfo[i].width = htm[0].width;
tl->lineInfo[i].height = htm[0].height;
for (j = 1; j < nFragments; j++) {
if (tl->lineInfo[i].x > htm[j].left)
tl->lineInfo[i].x = htm[j].left;
tl->lineInfo[i].width += htm[j].width;
}
delete[] htm;
tl->lineInfo[i].baseline = dlm[i].baseline;
}
delete[] dlm;
}
static std::map<uiDrawTextAlign, DWRITE_TEXT_ALIGNMENT> dwriteAligns = {
{ uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING },
{ uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER },
{ uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING },
};
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
uiDrawTextLayout *tl;
WCHAR *wDefaultFamily;
DWRITE_WORD_WRAPPING wrap;
FLOAT maxWidth;
HRESULT hr;
tl = uiNew(uiDrawTextLayout);
wDefaultFamily = toUTF16(p->DefaultFont->Family);
hr = dwfactory->CreateTextFormat(
wDefaultFamily, NULL,
uiprivWeightToDWriteWeight(p->DefaultFont->Weight),
uiprivItalicToDWriteStyle(p->DefaultFont->Italic),
uiprivStretchToDWriteStretch(p->DefaultFont->Stretch),
pointSizeToDWriteSize(p->DefaultFont->Size),
L"",
&(tl->format));
if (hr != S_OK)
logHRESULT(L"error creating IDWriteTextFormat", hr);
hr = tl->format->SetTextAlignment(dwriteAligns[p->Align]);
if (hr != S_OK)
logHRESULT(L"error applying text layout alignment", hr);
hr = dwfactory->CreateTextLayout(
(const WCHAR *) attrstrUTF16(p->String), attrstrUTF16Len(p->String),
tl->format,
FLT_MAX, FLT_MAX,
&(tl->layout));
if (hr != S_OK)
logHRESULT(L"error creating IDWriteTextLayout", hr);
wrap = DWRITE_WORD_WRAPPING_WRAP;
maxWidth = (FLOAT) (p->Width);
if (p->Width < 0) {
wrap = DWRITE_WORD_WRAPPING_NO_WRAP;
maxWidth = FLT_MAX; }
hr = tl->layout->SetWordWrapping(wrap);
if (hr != S_OK)
logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr);
hr = tl->layout->SetMaxWidth(maxWidth);
if (hr != S_OK)
logHRESULT(L"error setting IDWriteTextLayout max layout width", hr);
attrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs));
computeLineInfo(tl);
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));
uiFree(wDefaultFamily);
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
uiFree(tl->u16tou8);
uiFree(tl->u8tou16);
uiFree(tl->lineInfo);
delete tl->backgroundFuncs;
tl->layout->Release();
tl->format->Release();
uiFree(tl);
}
static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush)
{
D2D1_BRUSH_PROPERTIES props;
D2D1_COLOR_F color;
ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
props.opacity = 1.0;
props.transform._11 = 1;
props.transform._22 = 1;
color.r = r;
color.g = g;
color.b = b;
color.a = a;
return rt->CreateSolidColorBrush(
&color,
&props,
brush);
}
static ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)
{
ID2D1SolidColorBrush *brush;
HRESULT hr;
hr = mkSolidBrush(rt, r, g, b, a, &brush);
if (hr != S_OK)
logHRESULT(L"error creating solid brush", hr);
return brush;
}
class textRenderer : public IDWriteTextRenderer {
ULONG refcount;
ID2D1RenderTarget *rt;
BOOL snap;
ID2D1SolidColorBrush *black;
public:
textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)
{
this->refcount = 1;
this->rt = rt;
this->snap = snap;
this->black = black;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
{
if (ppvObject == NULL)
return E_POINTER;
if (riid == IID_IUnknown ||
riid == __uuidof (IDWritePixelSnapping) ||
riid == __uuidof (IDWriteTextRenderer)) {
this->AddRef();
*ppvObject = this;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
this->refcount++;
return this->refcount;
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
this->refcount--;
if (this->refcount == 0) {
delete this;
return 0;
}
return this->refcount;
}
virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform)
{
D2D1_MATRIX_3X2_F d2dtf;
if (transform == NULL)
return E_POINTER;
this->rt->GetTransform(&d2dtf);
transform->m11 = d2dtf._11;
transform->m12 = d2dtf._12;
transform->m21 = d2dtf._21;
transform->m22 = d2dtf._22;
transform->dx = d2dtf._31;
transform->dy = d2dtf._32;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip)
{
FLOAT dpix, dpiy;
if (pixelsPerDip == NULL)
return E_POINTER;
this->rt->GetDpi(&dpix, &dpiy);
*pixelsPerDip = dpix / 96;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled)
{
if (isDisabled == NULL)
return E_POINTER;
*isDisabled = !this->snap;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN *glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, IUnknown *clientDrawingEffect)
{
D2D1_POINT_2F baseline;
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
ID2D1SolidColorBrush *brush;
baseline.x = baselineOriginX;
baseline.y = baselineOriginY;
brush = this->black;
if (t != NULL && t->hasColor) {
HRESULT hr;
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
if (hr != S_OK)
return hr;
}
this->rt->DrawGlyphRun(
baseline,
glyphRun,
brush,
measuringMode);
if (t != NULL && t->hasColor)
brush->Release();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect)
{
if (inlineObject == NULL)
return E_POINTER;
return inlineObject->Draw(clientDrawingContext, this,
originX, originY,
isSideways, isRightToLeft,
clientDrawingEffect);
}
virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)
{
return E_UNEXPECTED;
}
virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)
{
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
ID2D1SolidColorBrush *brush;
D2D1_RECT_F rect;
D2D1::Matrix3x2F pixeltf;
FLOAT dpix, dpiy;
D2D1_POINT_2F pt;
if (underline == NULL)
return E_POINTER;
if (t == NULL) return E_UNEXPECTED;
brush = this->black;
if (t->hasUnderlineColor) {
HRESULT hr;
hr = mkSolidBrush(this->rt, t->ur, t->ug, t->ub, t->ua, &brush);
if (hr != S_OK)
return hr;
} else if (t->hasColor) {
HRESULT hr;
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
if (hr != S_OK)
return hr;
}
rect.left = baselineOriginX;
rect.top = baselineOriginY + underline->offset;
rect.right = rect.left + underline->width;
rect.bottom = rect.top + underline->thickness;
switch (t->u) {
case uiDrawUnderlineStyleSingle:
this->rt->FillRectangle(&rect, brush);
break;
case uiDrawUnderlineStyleDouble:
this->rt->GetTransform(&pixeltf);
this->rt->GetDpi(&dpix, &dpiy);
pixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96);
pt.x = 0;
pt.y = rect.top;
pt = pixeltf.TransformPoint(pt);
rect.top = (FLOAT) ((int) (pt.y + 0.5));
pixeltf.Invert();
pt = pixeltf.TransformPoint(pt);
rect.top = pt.y;
rect.top -= underline->thickness;
rect.bottom = rect.top + underline->thickness;
this->rt->FillRectangle(&rect, brush);
rect.top += 2 * underline->thickness;
rect.bottom = rect.top + underline->thickness;
this->rt->FillRectangle(&rect, brush);
break;
case uiDrawUnderlineStyleSuggestion:
{ ID2D1PathGeometry *path;
ID2D1GeometrySink *sink;
double amplitude, period, xOffset, yOffset;
double t;
bool first = true;
HRESULT hr;
hr = d2dfactory->CreatePathGeometry(&path);
if (hr != S_OK)
return hr;
hr = path->Open(&sink);
if (hr != S_OK)
return hr;
amplitude = underline->thickness;
period = 5 * underline->thickness;
xOffset = baselineOriginX;
yOffset = baselineOriginY + underline->offset;
for (t = 0; t < underline->width; t++) {
double x, angle, y;
D2D1_POINT_2F pt;
x = t + xOffset;
angle = 2 * uiPi * fmod(x, period) / period;
y = amplitude * sin(angle) + yOffset;
pt.x = x;
pt.y = y;
if (first) {
sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW);
first = false;
} else
sink->AddLine(pt);
}
sink->EndFigure(D2D1_FIGURE_END_OPEN);
hr = sink->Close();
if (hr != S_OK)
return hr;
sink->Release();
this->rt->DrawGeometry(path, brush, underline->thickness);
path->Release();
}
break;
}
if (t->hasUnderlineColor || t->hasColor)
brush->Release();
return S_OK;
}
};
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
D2D1_POINT_2F pt;
ID2D1SolidColorBrush *black;
textRenderer *renderer;
HRESULT hr;
for (const auto &f : *(tl->backgroundFuncs))
f(c, tl, x, y);
black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
#define renderD2D 0
#define renderOur 1
#if renderD2D
pt.x = x;
pt.y = y;
c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
#endif
#if renderD2D && renderOur
black->Release();
black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75);
#endif
#if renderOur
renderer = new textRenderer(c->rt,
TRUE, black);
hr = tl->layout->Draw(NULL,
renderer,
x, y);
if (hr != S_OK)
logHRESULT(L"error drawing IDWriteTextLayout", hr);
renderer->Release();
#endif
black->Release();
}
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
DWRITE_TEXT_METRICS metrics;
HRESULT hr;
hr = tl->layout->GetMetrics(&metrics);
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr);
*width = metrics.width;
*height = metrics.height;
}
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return tl->nLines;
}
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
*start = tl->lineInfo[line].startPos;
*start = tl->u16tou8[*start];
*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;
*end = tl->u16tou8[*end];
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
{
m->X = tl->lineInfo[line].x;
m->Y = tl->lineInfo[line].y;
m->Width = tl->lineInfo[line].width;
m->Height = tl->lineInfo[line].height;
m->BaselineY = tl->lineInfo[line].y + tl->lineInfo[line].baseline;
m->Ascent = tl->lineInfo[line].baseline;
m->Descent = tl->lineInfo[line].height - tl->lineInfo[line].baseline;
m->Leading = 0;
m->ParagraphSpacingBefore = 0; m->LineHeightSpace = 0; m->LineSpacing = 0; m->ParagraphSpacing = 0; }
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
{
DWRITE_HIT_TEST_METRICS m;
BOOL trailing, inside;
size_t p;
UINT32 i;
HRESULT hr;
hr = tl->layout->HitTestPoint(x, y,
&trailing, &inside,
&m);
if (hr != S_OK)
logHRESULT(L"error hit-testing IDWriteTextLayout", hr);
p = m.textPosition;
if (trailing) {
DWRITE_HIT_TEST_METRICS m2;
FLOAT x, y;
hr = tl->layout->HitTestTextPosition(m.textPosition, trailing,
&x, &y, &m2);
if (hr != S_OK)
logHRESULT(L"error aligning trailing hit to nearest cluster", hr);
p = m2.textPosition + m2.length;
}
*pos = tl->u16tou8[p];
for (i = 0; i < tl->nLines; i++) {
double ltop, lbottom;
ltop = tl->lineInfo[i].y;
lbottom = ltop + tl->lineInfo[i].height;
if (y < lbottom)
break;
}
if (i == tl->nLines)
i--;
*line = i;
}
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
{
BOOL trailing;
DWRITE_HIT_TEST_METRICS m;
FLOAT x, y;
HRESULT hr;
if (line < 0 || line >= tl->nLines)
return -1;
pos = tl->u8tou16[pos];
if (pos < tl->lineInfo[line].startPos || pos > tl->lineInfo[line].endPos)
return -1;
trailing = FALSE;
if (pos != 0 && pos != tl->nUTF16 && pos == tl->lineInfo[line].endPos) {
pos--;
trailing = TRUE;
}
hr = tl->layout->HitTestTextPosition(pos, trailing,
&x, &y, &m);
if (hr != S_OK)
logHRESULT(L"error calling IDWriteTextLayout::HitTestTextPosition()", hr);
return x;
}
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
{
DWORD caretWidth;
p->r = 0.0;
p->g = 0.0;
p->b = 0.0;
p->a = 1.0;
if (SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caretWidth, 0) == 0)
caretWidth = GetSystemMetrics(SM_CXBORDER);
{
FLOAT dpix, dpiy;
c->rt->GetDpi(&dpix, &dpiy);
p->width = ((double) (caretWidth * 96)) / dpix;
}
p->xoff = 0;
}