From 721269b3eb93cb8a038f8fae223baf7012107e7b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 19 Apr 2016 18:45:16 -0400 Subject: [PATCH] Implemented colored text on OS X and Windows. --- darwin/drawtext.m | 1 - windows/draw.c | 14 +++++ windows/drawtext.cpp | 114 +++++++++++++++++++++++++++++++-------- windows/uipriv_windows.h | 1 + 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/darwin/drawtext.m b/darwin/drawtext.m index e56e1763..86fcb753 100644 --- a/darwin/drawtext.m +++ b/darwin/drawtext.m @@ -601,7 +601,6 @@ void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextL CGContextSetTextPosition(c, x, y); #endif -// TODO length - 1? #define rangeToCFRange() CFRangeMake(startChar, endChar - startChar) void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a) diff --git a/windows/draw.c b/windows/draw.c index 8cd12057..d45f787e 100644 --- a/windows/draw.c +++ b/windows/draw.c @@ -890,3 +890,17 @@ void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) doDrawText(c->rt, black, x, y, layout); ID2D1Brush_Release(black); } + +// TODO this is a mess +ID2D1Brush *createSolidColorBrushInternal(ID2D1RenderTarget *rt, double r, double g, double b, double a) +{ + uiDrawBrush brush; + + ZeroMemory(&brush, sizeof (uiDrawBrush)); + brush.Type = uiDrawBrushTypeSolid; + brush.R = r; + brush.G = g; + brush.B = b; + brush.A = a; + return makeBrush(&brush, rt); +} diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index d320d1a2..b86e6890 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -1,5 +1,7 @@ // 22 december 2015 #include "uipriv_windows.h" +// TODO +#include // notes: // only available in windows 8 and newer: @@ -315,15 +317,30 @@ void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metri metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size); } +// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance +// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time +enum layoutAttrType { + layoutAttrColor, +}; + +struct layoutAttr { + enum layoutAttrType type; + intmax_t start; + intmax_t end; + double components[4]; +}; + struct uiDrawTextLayout { + WCHAR *text; + size_t textlen; + double width; IDWriteTextFormat *format; - IDWriteTextLayout *layout; + std::vector *attrs; }; uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width) { uiDrawTextLayout *layout; - WCHAR *wtext; HRESULT hr; layout = uiNew(uiDrawTextLayout); @@ -343,73 +360,128 @@ uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultF if (hr != S_OK) logHRESULT("error creating IDWriteTextFormat in uiDrawNewTextLayout()", hr); - wtext = toUTF16(text); - hr = dwfactory->CreateTextLayout(wtext, wcslen(wtext), - layout->format, - // FLOAT is float, not double, so this should work... TODO - FLT_MAX, FLT_MAX, - &(layout->layout)); - if (hr != S_OK) - logHRESULT("error creating IDWriteTextLayout in uiDrawNewTextLayout()", hr); - uiFree(wtext); + layout->text = toUTF16(text); + layout->textlen = wcslen(layout->text); uiDrawTextLayoutSetWidth(layout, width); + layout->attrs = new std::vector; + return layout; } void uiDrawFreeTextLayout(uiDrawTextLayout *layout) { - layout->layout->Release(); + delete layout->attrs; layout->format->Release(); + uiFree(layout->text); uiFree(layout); } -void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) +IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt) { + IDWriteTextLayout *dl; + DWRITE_TEXT_RANGE range; + IUnknown *unkBrush; DWRITE_WORD_WRAPPING wrap; FLOAT maxWidth; HRESULT hr; + hr = dwfactory->CreateTextLayout(layout->text, layout->textlen, + layout->format, + // FLOAT is float, not double, so this should work... TODO + FLT_MAX, FLT_MAX, + &dl); + if (hr != S_OK) + logHRESULT("error creating IDWriteTextLayout in prepareLayout()", hr); + + for (const struct layoutAttr &attr : *(layout->attrs)) { + range.startPosition = attr.start; + range.length = attr.end - attr.start; + switch (attr.type) { + case layoutAttrColor: + if (rt == NULL) // determining extents, not drawing + break; + unkBrush = createSolidColorBrushInternal(rt, attr.components[0], attr.components[1], attr.components[2], attr.components[3]); + hr = dl->SetDrawingEffect(unkBrush, range); + unkBrush->Release(); // associated with dl + break; + default: + hr = E_FAIL; + logHRESULT("invalid text attribute type in prepareLayout()", hr); + } + if (hr != S_OK) + logHRESULT("error adding attribute to text layout in prepareLayout()", hr); + } + + // and set the width // this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 wrap = DWRITE_WORD_WRAPPING_WRAP; - maxWidth = width; - if (width < 0) { + maxWidth = layout->width; + if (layout->width < 0) { wrap = DWRITE_WORD_WRAPPING_NO_WRAP; // setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe maxWidth = FLT_MAX; // see TODO above } - hr = layout->layout->SetWordWrapping(wrap); + hr = dl->SetWordWrapping(wrap); if (hr != S_OK) - logHRESULT("error setting word wrapping mode in uiDrawTextLayoutSetWidth()", hr); - hr = layout->layout->SetMaxWidth(maxWidth); + logHRESULT("error setting word wrapping mode in prepareLayout()", hr); + hr = dl->SetMaxWidth(maxWidth); if (hr != S_OK) - logHRESULT("error setting max layout width in uiDrawTextLayoutSetWidth()", hr); + logHRESULT("error setting max layout width in prepareLayout()", hr); + + return dl; +} + + +void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) +{ + layout->width = width; } // TODO for a single line the height includes the leading; it should not void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) { + IDWriteTextLayout *dl; DWRITE_TEXT_METRICS metrics; HRESULT hr; - hr = layout->layout->GetMetrics(&metrics); + dl = prepareLayout(layout, NULL); + hr = dl->GetMetrics(&metrics); if (hr != S_OK) logHRESULT("error getting layout metrics in uiDrawTextLayoutExtents()", hr); *width = metrics.width; // TODO make sure the behavior of this on empty strings is the same on all platforms *height = metrics.height; + dl->Release(); } void doDrawText(ID2D1RenderTarget *rt, ID2D1Brush *black, double x, double y, uiDrawTextLayout *layout) { + IDWriteTextLayout *dl; D2D1_POINT_2F pt; HRESULT hr; + dl = prepareLayout(layout, rt); pt.x = x; pt.y = y; // TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP? // TODO D2D1_DRAW_TEXT_OPTIONS_CLIP? // TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? - rt->DrawTextLayout(pt, layout->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE); + rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE); + dl->Release(); +} + +void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a) +{ + struct layoutAttr attr; + + attr.type = layoutAttrColor; + attr.start = startChar; + attr.end = endChar; + attr.components[0] = r; + attr.components[1] = g; + attr.components[2] = b; + attr.components[3] = a; + layout->attrs->push_back(attr); } diff --git a/windows/uipriv_windows.h b/windows/uipriv_windows.h index d84a3af5..9ea1dabd 100644 --- a/windows/uipriv_windows.h +++ b/windows/uipriv_windows.h @@ -140,6 +140,7 @@ extern void uninitDraw(void); extern ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd); extern uiDrawContext *newContext(ID2D1RenderTarget *); extern void freeContext(uiDrawContext *); +extern ID2D1Brush *createSolidColorBrushInternal(ID2D1RenderTarget *rt, double r, double g, double b, double a); // dwrite.cpp #ifdef __cplusplus