diff --git a/windows/attrstr.cpp b/windows/attrstr.cpp index 10e9002a..31fefcae 100644 --- a/windows/attrstr.cpp +++ b/windows/attrstr.cpp @@ -2,19 +2,42 @@ #include "uipriv_windows.hpp" #include "draw.hpp" -// we need to collect all the OpenType features and background blocks and add them all at once +// we need to combine color and underline style into one unit for IDWriteLayout::SetDrawingEffect() +// we also need to collect all the OpenType features and background blocks and add them all at once // TODO(TODO does not seem to apply here) this is the wrong approach; it causes Pango to end runs early, meaning attributes like the ligature attributes never get applied properly // TODO contextual alternates override ligatures? // TODO rename this struct to something that isn't exclusively foreach-ing? struct foreachParams { const uint16_t *s; IDWriteTextLayout *layout; + std::map *effects; std::map *features; //TODO GPtrArray *backgroundClosures; }; #define isCodepointStart(w) ((((uint16_t) (w)) < 0xDC00) || (((uint16_t) (w)) >= 0xE000)) +static void ensureEffectsInRange(struct foreachParams *p, size_t start, size_t end, std::function f) +{ + size_t i; + size_t *key; + textDrawingEffect *t; + + for (i = start; i < end; i++) { + // don't create redundant entries; see below + if (!isCodepointStart(p->s[i])) + continue; + t = (*(p->effects))[i]; + if (t != NULL) { + f(t); + continue; + } + t = new textDrawingEffect; + f(t); + (*(p->effects))[i] = t; + } +} + static void ensureFeaturesInRange(struct foreachParams *p, size_t start, size_t end) { size_t i; @@ -124,9 +147,8 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t #if 0 /* TODO */ GClosure *closure; PangoGravity gravity; - PangoUnderline underline; - PangoLanguage *lang; #endif + WCHAR *localeName; struct otParam op; HRESULT hr; @@ -151,30 +173,38 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t if (hr != S_OK) logHRESULT(L"error applying size attribute", hr); break; -#if 0 /* TODO */ case uiAttributeWeight: - // TODO reverse the misalignment from drawtext.c if it is corrected - addattr(p, start, end, - pango_attr_weight_new((PangoWeight) (spec->Value))); + // TODO reverse the misalignment from drawtext.cpp if it is corrected + hr = p->layout->SetFontWeight( + (DWRITE_FONT_WEIGHT) (spec->Value), + range); + if (hr != S_OK) + logHRESULT(L"error applying weight attribute", hr); break; case uiAttributeItalic: - addattr(p, start, end, - pango_attr_style_new(pangoItalics[(uiDrawTextItalic) (spec->Value)])); + hr = p->layout->SetFontStyle( + dwriteItalics[(uiDrawTextItalic) (spec->Value)], + range); + if (hr != S_OK) + logHRESULT(L"error applying italic attribute", hr); break; case uiAttributeStretch: - addattr(p, start, end, - pango_attr_stretch_new(pangoStretches[(uiDrawTextStretch) (spec->Value)])); + hr = p->layout->SetFontStretch( + dwriteStretches[(uiDrawTextStretch) (spec->Value)], + range); + if (hr != S_OK) + logHRESULT(L"error applying stretch attribute", hr); break; case uiAttributeColor: - addattr(p, start, end, - pango_attr_foreground_new( - (guint16) (spec->R * 65535.0), - (guint16) (spec->G * 65535.0), - (guint16) (spec->B * 65535.0))); - addattr(p, start, end, - FUTURE_pango_attr_foreground_alpha_new( - (guint16) (spec->A * 65535.0))); + ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) { + t->hasColor = true; + t->r = spec->R; + t->g = spec->G; + t->b = spec->B; + t->a = spec->A; + }); break; +#if 0 case uiAttributeBackground: closure = mkBackgroundClosure(start, end, spec->R, spec->G, spec->B, spec->A); @@ -231,14 +261,15 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t break; } break; - // language strings are specified as BCP 47: https://developer.gnome.org/pango/1.30/pango-Scripts-and-Languages.html#pango-language-from-string https://www.ietf.org/rfc/rfc3066.txt - case uiAttributeLanguage: - lang = pango_language_from_string((const char *) (spec->Value)); - addattr(p, start, end, - pango_attr_language_new(lang)); - // lang *cannot* be freed - 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)); + hr = p->layout->SetLocaleName(localeName, range); + if (hr != S_OK) + logHRESULT(L"error applying locale name attribute", hr); + uiFree(localeName); + break; // TODO default: // handle typographic features @@ -252,6 +283,25 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t return 0; } +static void applyAndFreeEffectsAttributes(struct foreachParams *p) +{ + DWRITE_TEXT_RANGE range; + HRESULT hr; + + for (auto iter = p->effects->begin(); iter != p->effects->end(); iter++) { + // make sure we cover an entire code point + range.startPosition = iter->first; + range.length = 1; + if (!isCodepointStart(p->s[iter->first])) + range.length = 2; + hr = p->layout->SetDrawingEffect(iter->second, range); + if (hr != S_OK) + logHRESULT(L"error applying drawing effects attributes", hr); + iter->second->Release(); + } + delete p->effects; +} + static void applyAndFreeFeatureAttributes(struct foreachParams *p) { DWRITE_TEXT_RANGE range; @@ -265,7 +315,7 @@ static void applyAndFreeFeatureAttributes(struct foreachParams *p) range.length = 2; hr = p->layout->SetTypography(iter->second, range); if (hr != S_OK) - logHRESULT(L"error applying typographic features", hr); + logHRESULT(L"error applying typographic features attributes", hr); iter->second->Release(); } delete p->features; @@ -284,9 +334,11 @@ void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayou fep.s = attrstrUTF16(p->String); fep.layout = layout; + fep.effects = new std::map; fep.features = new std::map; //TODO fep.backgroundClosures = g_ptr_array_new_with_free_func(unrefClosure); uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); + applyAndFreeEffectsAttributes(&fep); applyAndFreeFeatureAttributes(&fep); //TODO *backgroundClosures = fep.backgroundClosures; } diff --git a/windows/draw.hpp b/windows/draw.hpp index ce177523..adc68294 100644 --- a/windows/draw.hpp +++ b/windows/draw.hpp @@ -17,3 +17,65 @@ extern void m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d); // attrstr.cpp extern void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout/*TODO, GPtrArray **backgroundClosures*/); + +// drawtext.cpp +class textDrawingEffect : public IUnknown { + ULONG refcount; +public: + bool hasColor; + double r; + double g; + double b; + double a; + + bool hasUnderline; + uiDrawUnderlineStyle u; + + bool hasUnderlineColor; + double ur; + double ug; + double ub; + double ua; + + textDrawingEffect() + { + this->refcount = 1; + this->hasColor = false; + this->hasUnderline = false; + this->hasUnderlineColor = false; + } + + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) + { + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown) { + 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; + } + +}; +// TODO these should not be exported +extern std::map dwriteItalics; +extern std::map dwriteStretches; diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index 9f321f12..31d6daee 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -28,14 +28,14 @@ struct uiDrawTextLayout { #define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) // TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) -static std::map dwriteItalics = { +std::map dwriteItalics = { { uiDrawTextItalicNormal, DWRITE_FONT_STYLE_NORMAL }, { uiDrawTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE }, { uiDrawTextItalicItalic, DWRITE_FONT_STYLE_ITALIC }, }; // TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) -static std::map dwriteStretches = { +std::map dwriteStretches = { { uiDrawTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED }, { uiDrawTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED }, { uiDrawTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED }, @@ -219,12 +219,10 @@ void uiDrawFreeTextLayout(uiDrawTextLayout *tl) uiFree(tl); } -static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a) +static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush) { D2D1_BRUSH_PROPERTIES props; D2D1_COLOR_F color; - ID2D1SolidColorBrush *brush; - HRESULT hr; ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES)); props.opacity = 1.0; @@ -235,10 +233,18 @@ static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, doubl color.g = g; color.b = b; color.a = a; - hr = rt->CreateSolidColorBrush( + return rt->CreateSolidColorBrush( &color, &props, - &brush); + 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; @@ -250,9 +256,9 @@ class textRenderer : public IDWriteTextRenderer { ULONG refcount; ID2D1RenderTarget *rt; BOOL snap; - IUnknown *black; + ID2D1SolidColorBrush *black; public: - textRenderer(ID2D1RenderTarget *rt, BOOL snap, IUnknown *black) + textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black) { this->refcount = 1; this->rt = rt; @@ -332,16 +338,26 @@ public: 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; - if (clientDrawingEffect == NULL) - clientDrawingEffect = black; + 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, - (ID2D1Brush *) clientDrawingEffect, + brush, measuringMode); + if (t != NULL && t->hasColor) + brush->Release(); return S_OK; } @@ -357,12 +373,16 @@ 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; } virtual HRESULT DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect) { + textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect; + // TODO return S_OK; } @@ -372,13 +392,13 @@ public: void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) { D2D1_POINT_2F pt; - ID2D1Brush *black; + ID2D1SolidColorBrush *black; textRenderer *renderer; HRESULT hr; // 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 = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); + black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); #define renderD2D 0 #define renderOur 1 @@ -395,7 +415,7 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) // draw ours semitransparent so we can check // TODO get the actual color Charles Petzold uses and use that black->Release(); - black = mkSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75); + black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75); #endif #if renderOur renderer = new textRenderer(c->rt, diff --git a/windows/winapi.hpp b/windows/winapi.hpp index a540809c..c5796faf 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -47,4 +47,5 @@ #include #include #include +#include #endif