From f2b158b529e521ecac2bf0a4d233a6f44b839ae8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 22 Feb 2017 19:13:36 -0500 Subject: [PATCH] And finished implementing attributes on Windows. --- examples/drawtext/attributes.c | 10 +- windows/attrstr.cpp | 192 ++++++++++++--------------------- windows/draw.hpp | 3 +- windows/drawtext.cpp | 120 +++++++++++++++++++-- 4 files changed, 194 insertions(+), 131 deletions(-) diff --git a/examples/drawtext/attributes.c b/examples/drawtext/attributes.c index f0868a8b..24dd4a19 100644 --- a/examples/drawtext/attributes.c +++ b/examples/drawtext/attributes.c @@ -100,7 +100,15 @@ static void setupAttributedString(void) spec.Type = uiAttributeVerticalForms; spec.Value = 1; uiAttributedStringSetAttribute(attrstr, &spec, start, end); - uiAttributedStringAppendUnattributed(attrstr, " (which you can draw rotated for proper vertical text)"); + uiAttributedStringAppendUnattributed(attrstr, " (which you can draw rotated for proper vertical text; for instance, "); + next = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeVerticalForms; + spec.Value = 1; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); uiAttributedStringAppendUnattributed(attrstr, ", "); diff --git a/windows/attrstr.cpp b/windows/attrstr.cpp index 31fefcae..c6d366c5 100644 --- a/windows/attrstr.cpp +++ b/windows/attrstr.cpp @@ -12,7 +12,7 @@ struct foreachParams { IDWriteTextLayout *layout; std::map *effects; std::map *features; -//TODO GPtrArray *backgroundClosures; + std::vector *backgroundFuncs; }; #define isCodepointStart(w) ((((uint16_t) (w)) < 0xDC00) || (((uint16_t) (w)) >= 0xE000)) @@ -59,54 +59,20 @@ static void ensureFeaturesInRange(struct foreachParams *p, size_t start, size_t } } -#if 0 /* TODO */ -struct closureParams { - size_t start; - size_t end; - double r; - double g; - double b; - double a; -}; - -static void backgroundClosure(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y, gpointer data) +static backgroundFunc mkBackgroundFunc(size_t start, size_t end, double r, double g, double b, double a) { - struct closureParams *p = (struct closureParams *) data; - uiDrawBrush brush; + return [=](uiDrawContext *c, uiDrawTextLayout *layout, double x, double y) { + uiDrawBrush brush; - brush.Type = uiDrawBrushTypeSolid; - brush.R = p->r; - brush.G = p->g; - brush.B = p->b; - brush.A = p->a; - drawTextBackground(c, x, y, layout, p->start, p->end, &brush, 0); + brush.Type = uiDrawBrushTypeSolid; + brush.R = r; + brush.G = g; + brush.B = b; + brush.A = a; + drawTextBackground(c, x, y, layout, start, end, &brush, 0); + }; } -static void freeClosureParams(gpointer data, GClosure *closure) -{ - uiFree((struct closureParams *) data); -} - -static GClosure *mkBackgroundClosure(size_t start, size_t end, double r, double g, double b, double a) -{ - struct closureParams *p; - GClosure *closure; - - p = uiNew(struct closureParams); - p->start = start; - p->end = end; - p->r = r; - p->g = g; - p->b = b; - p->a = a; - closure = (GClosure *) g_cclosure_new(G_CALLBACK(backgroundClosure), p, freeClosureParams); - // TODO write a specific marshaler - // TODO or drop the closure stuff entirely - g_closure_set_marshal(closure, g_cclosure_marshal_generic); - return closure; -} -#endif - struct otParam { struct foreachParams *p; size_t start; @@ -144,10 +110,8 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t struct foreachParams *p = (struct foreachParams *) data; DWRITE_TEXT_RANGE range; WCHAR *wfamily; -#if 0 /* TODO */ - GClosure *closure; - PangoGravity gravity; -#endif + BOOL hasUnderline; + uint32_t vertval; WCHAR *localeName; struct otParam op; HRESULT hr; @@ -204,64 +168,79 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t t->a = spec->A; }); break; -#if 0 case uiAttributeBackground: - closure = mkBackgroundClosure(start, end, - spec->R, spec->G, spec->B, spec->A); - g_ptr_array_add(p->backgroundClosures, closure); + p->backgroundFuncs->push_back( + mkBackgroundFunc(start, end, + spec->R, spec->G, spec->B, spec->A)); break; case uiAttributeVerticalForms: - gravity = PANGO_GRAVITY_SOUTH; + // LONGTERM 8 and/or 8.1 add other methods for vertical text + op.p = p; + op.start = start; + op.end = end; + vertval = 0; if (spec->Value != 0) - gravity = PANGO_GRAVITY_EAST; - addattr(p, start, end, - pango_attr_gravity_new(gravity)); + vertval = 1; + doOpenType("vert", vertval, &op); + doOpenType("vrt2", vertval, &op); + doOpenType("vkrn", vertval, &op); + doOpenType("vrtr", vertval, &op); break; case uiAttributeUnderline: - switch (spec->Value) { - case uiDrawUnderlineStyleNone: - underline = PANGO_UNDERLINE_NONE; - break; - case uiDrawUnderlineStyleSingle: - underline = PANGO_UNDERLINE_SINGLE; - break; - case uiDrawUnderlineStyleDouble: - underline = PANGO_UNDERLINE_DOUBLE; - break; - case uiDrawUnderlineStyleSuggestion: - underline = PANGO_UNDERLINE_ERROR; - break; - } - addattr(p, start, end, - pango_attr_underline_new(underline)); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasUnderline = true; + t->u = (uiDrawUnderlineStyle) (spec->Value); + }); + // mark that we have an underline; otherwise, DirectWrite will never call our custom renderer's DrawUnderline() method + hasUnderline = FALSE; + if ((uiDrawUnderlineStyle) (spec->Value) != uiDrawUnderlineStyleNone) + hasUnderline = TRUE; + hr = p->layout->SetUnderline(hasUnderline, range); + if (hr != S_OK) + logHRESULT(L"error applying underline attribute", hr); break; case uiAttributeUnderlineColor: switch (spec->Value) { case uiDrawUnderlineColorCustom: - addattr(p, start, end, - pango_attr_underline_color_new( - (guint16) (spec->R * 65535.0), - (guint16) (spec->G * 65535.0), - (guint16) (spec->B * 65535.0))); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasUnderlineColor = true; + t->ur = spec->R; + t->ug = spec->G; + t->ub = spec->B; + t->ua = spec->A; + }); break; + // TODO see if Microsoft has any standard colors for this case uiDrawUnderlineColorSpelling: // TODO GtkTextView style property error-underline-color - addattr(p, start, end, - pango_attr_underline_color_new(65535, 0, 0)); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasUnderlineColor = true; + t->ur = 1.0; + t->ug = 0.0; + t->ub = 0.0; + t->ua = 1.0; + }); break; case uiDrawUnderlineColorGrammar: - // TODO find a more appropriate color - addattr(p, start, end, - pango_attr_underline_color_new(0, 65535, 0)); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasUnderlineColor = true; + t->ur = 0.0; + t->ug = 1.0; + t->ub = 0.0; + t->ua = 1.0; + }); break; case uiDrawUnderlineColorAuxiliary: - // TODO find a more appropriate color - addattr(p, start, end, - pango_attr_underline_color_new(0, 0, 65535)); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasUnderlineColor = true; + t->ur = 0.0; + t->ug = 0.0; + t->ub = 1.0; + t->ua = 1.0; + }); break; } break; -#endif // locale names are specified as BCP 47: https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx https://www.ietf.org/rfc/rfc4646.txt case uiAttributeLanguage: localeName = toUTF16((char *) (spec->Value)); @@ -321,14 +300,7 @@ static void applyAndFreeFeatureAttributes(struct foreachParams *p) delete p->features; } -#if 0 /* TODO */ -static void unrefClosure(gpointer data) -{ - g_closure_unref((GClosure *) data); -} -#endif - -void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout/*TODO, GPtrArray **backgroundClosures*/) +void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector **backgroundFuncs) { struct foreachParams fep; @@ -336,35 +308,9 @@ void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayou fep.layout = layout; fep.effects = new std::map; fep.features = new std::map; -//TODO fep.backgroundClosures = g_ptr_array_new_with_free_func(unrefClosure); + fep.backgroundFuncs = new std::vector; uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); applyAndFreeEffectsAttributes(&fep); applyAndFreeFeatureAttributes(&fep); -//TODO *backgroundClosures = fep.backgroundClosures; + *backgroundFuncs = fep.backgroundFuncs; } - -#if 0 -void invokeBackgroundClosure(GClosure *closure, uiDrawContext *c, uiDrawTextLayout *layout, double x, double y) -{ - GValue values[4] = { - // the zero-initialization is needed for g_value_init() to work - G_VALUE_INIT, - G_VALUE_INIT, - G_VALUE_INIT, - G_VALUE_INIT, - }; - - g_value_init(values + 0, G_TYPE_POINTER); - g_value_set_pointer(values + 0, c); - g_value_init(values + 1, G_TYPE_POINTER); - g_value_set_pointer(values + 1, layout); - g_value_init(values + 2, G_TYPE_DOUBLE); - g_value_set_double(values + 2, x); - g_value_init(values + 3, G_TYPE_DOUBLE); - g_value_set_double(values + 3, y); - g_closure_invoke(closure, - NULL, - 4, values, - NULL); -} -#endif diff --git a/windows/draw.hpp b/windows/draw.hpp index adc68294..f3a8d387 100644 --- a/windows/draw.hpp +++ b/windows/draw.hpp @@ -16,7 +16,8 @@ extern ID2D1PathGeometry *pathGeometry(uiDrawPath *p); extern void m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d); // attrstr.cpp -extern void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout/*TODO, GPtrArray **backgroundClosures*/); +typedef std::function backgroundFunc; +extern void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector **backgroundFuncs); // drawtext.cpp class textDrawingEffect : public IUnknown { diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index 31d6daee..3510a379 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -6,12 +6,14 @@ // - consider the warnings about antialiasing in the PadWrite sample // - if that's not a problem, do we have overlapping rects in the hittest sample? I can't tell... // - what happens if any nLines == 0? +// - paragraph alignment is subject to RTL mirroring; see if it is on other platforms // TODO verify our renderer is correct, especially with regards to snapping struct uiDrawTextLayout { IDWriteTextFormat *format; IDWriteTextLayout *layout; + std::vector *backgroundFuncs; UINT32 nLines; struct lineInfo *lineInfo; // for converting DirectWrite indices from/to byte offsets @@ -196,7 +198,7 @@ uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) if (hr != S_OK) logHRESULT(L"error setting IDWriteTextLayout max layout width", hr); - attrstrToIDWriteTextLayoutAttrs(p, tl->layout); + attrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs)); computeLineInfo(tl); @@ -214,6 +216,7 @@ void uiDrawFreeTextLayout(uiDrawTextLayout *tl) uiFree(tl->u16tou8); uiFree(tl->u8tou16); uiFree(tl->lineInfo); + delete tl->backgroundFuncs; tl->layout->Release(); tl->format->Release(); uiFree(tl); @@ -373,17 +376,118 @@ public: virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect) { - textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect; - - // TODO - return S_OK; + // we don't support strikethrough + return E_UNEXPECTED; } virtual HRESULT 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; - // TODO + if (underline == NULL) + return E_POINTER; + if (t == NULL) // we can only get here through an underline + 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) { + // TODO formalize this rule + 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: + // TODO do any of the matrix methods return errors? + // TODO standardize double-underline shape across platforms? wavy underline shape? + 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; + // first line + rect.top -= underline->thickness; + // and it seems we need to recompute this + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + // second line + rect.top += 2 * underline->thickness; + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + break; + case uiDrawUnderlineStyleSuggestion: + { // TODO get rid of the extra block + // TODO properly clean resources on failure + // TODO use fully qualified C overloads for all methods + // TODO ensure all methods properly have errors handled + 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; } }; @@ -396,6 +500,10 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) textRenderer *renderer; HRESULT hr; + // TODO the "any combination of the above" one isn't drawn in the right place but the "multiple backgrounds" one is (at least for when there's a line break; TODO) + for (const auto &f : *(tl->backgroundFuncs)) + f(c, tl, x, y); + // TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms // TODO figure out if this needs to be cleaned out black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);