More text attributes on Windows, including the beginning of drawing effects for colors and underlines.

This commit is contained in:
Pietro Gagliardi 2017-02-22 15:19:11 -05:00
parent fb04feaebb
commit b42250e3a9
4 changed files with 177 additions and 42 deletions

View File

@ -2,19 +2,42 @@
#include "uipriv_windows.hpp" #include "uipriv_windows.hpp"
#include "draw.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(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 contextual alternates override ligatures?
// TODO rename this struct to something that isn't exclusively foreach-ing? // TODO rename this struct to something that isn't exclusively foreach-ing?
struct foreachParams { struct foreachParams {
const uint16_t *s; const uint16_t *s;
IDWriteTextLayout *layout; IDWriteTextLayout *layout;
std::map<size_t, textDrawingEffect *> *effects;
std::map<size_t, IDWriteTypography *> *features; std::map<size_t, IDWriteTypography *> *features;
//TODO GPtrArray *backgroundClosures; //TODO GPtrArray *backgroundClosures;
}; };
#define isCodepointStart(w) ((((uint16_t) (w)) < 0xDC00) || (((uint16_t) (w)) >= 0xE000)) #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<void(textDrawingEffect *)> 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) static void ensureFeaturesInRange(struct foreachParams *p, size_t start, size_t end)
{ {
size_t i; size_t i;
@ -124,9 +147,8 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t
#if 0 /* TODO */ #if 0 /* TODO */
GClosure *closure; GClosure *closure;
PangoGravity gravity; PangoGravity gravity;
PangoUnderline underline;
PangoLanguage *lang;
#endif #endif
WCHAR *localeName;
struct otParam op; struct otParam op;
HRESULT hr; HRESULT hr;
@ -151,30 +173,38 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t
if (hr != S_OK) if (hr != S_OK)
logHRESULT(L"error applying size attribute", hr); logHRESULT(L"error applying size attribute", hr);
break; break;
#if 0 /* TODO */
case uiAttributeWeight: case uiAttributeWeight:
// TODO reverse the misalignment from drawtext.c if it is corrected // TODO reverse the misalignment from drawtext.cpp if it is corrected
addattr(p, start, end, hr = p->layout->SetFontWeight(
pango_attr_weight_new((PangoWeight) (spec->Value))); (DWRITE_FONT_WEIGHT) (spec->Value),
range);
if (hr != S_OK)
logHRESULT(L"error applying weight attribute", hr);
break; break;
case uiAttributeItalic: case uiAttributeItalic:
addattr(p, start, end, hr = p->layout->SetFontStyle(
pango_attr_style_new(pangoItalics[(uiDrawTextItalic) (spec->Value)])); dwriteItalics[(uiDrawTextItalic) (spec->Value)],
range);
if (hr != S_OK)
logHRESULT(L"error applying italic attribute", hr);
break; break;
case uiAttributeStretch: case uiAttributeStretch:
addattr(p, start, end, hr = p->layout->SetFontStretch(
pango_attr_stretch_new(pangoStretches[(uiDrawTextStretch) (spec->Value)])); dwriteStretches[(uiDrawTextStretch) (spec->Value)],
range);
if (hr != S_OK)
logHRESULT(L"error applying stretch attribute", hr);
break; break;
case uiAttributeColor: case uiAttributeColor:
addattr(p, start, end, ensureEffectsInRange(p, start, end, [=](textDrawingEffect *t) {
pango_attr_foreground_new( t->hasColor = true;
(guint16) (spec->R * 65535.0), t->r = spec->R;
(guint16) (spec->G * 65535.0), t->g = spec->G;
(guint16) (spec->B * 65535.0))); t->b = spec->B;
addattr(p, start, end, t->a = spec->A;
FUTURE_pango_attr_foreground_alpha_new( });
(guint16) (spec->A * 65535.0)));
break; break;
#if 0
case uiAttributeBackground: case uiAttributeBackground:
closure = mkBackgroundClosure(start, end, closure = mkBackgroundClosure(start, end,
spec->R, spec->G, spec->B, spec->A); spec->R, spec->G, spec->B, spec->A);
@ -231,14 +261,15 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t
break; break;
} }
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 #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 // TODO
default: default:
// handle typographic features // handle typographic features
@ -252,6 +283,25 @@ static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t
return 0; 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) static void applyAndFreeFeatureAttributes(struct foreachParams *p)
{ {
DWRITE_TEXT_RANGE range; DWRITE_TEXT_RANGE range;
@ -265,7 +315,7 @@ static void applyAndFreeFeatureAttributes(struct foreachParams *p)
range.length = 2; range.length = 2;
hr = p->layout->SetTypography(iter->second, range); hr = p->layout->SetTypography(iter->second, range);
if (hr != S_OK) if (hr != S_OK)
logHRESULT(L"error applying typographic features", hr); logHRESULT(L"error applying typographic features attributes", hr);
iter->second->Release(); iter->second->Release();
} }
delete p->features; delete p->features;
@ -284,9 +334,11 @@ void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayou
fep.s = attrstrUTF16(p->String); fep.s = attrstrUTF16(p->String);
fep.layout = layout; fep.layout = layout;
fep.effects = new std::map<size_t, textDrawingEffect *>;
fep.features = new std::map<size_t, IDWriteTypography *>; fep.features = new std::map<size_t, IDWriteTypography *>;
//TODO fep.backgroundClosures = g_ptr_array_new_with_free_func(unrefClosure); //TODO fep.backgroundClosures = g_ptr_array_new_with_free_func(unrefClosure);
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
applyAndFreeEffectsAttributes(&fep);
applyAndFreeFeatureAttributes(&fep); applyAndFreeFeatureAttributes(&fep);
//TODO *backgroundClosures = fep.backgroundClosures; //TODO *backgroundClosures = fep.backgroundClosures;
} }

View File

@ -17,3 +17,65 @@ extern void m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d);
// attrstr.cpp // attrstr.cpp
extern void attrstrToIDWriteTextLayoutAttrs(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout/*TODO, GPtrArray **backgroundClosures*/); 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<uiDrawTextItalic, DWRITE_FONT_STYLE> dwriteItalics;
extern std::map<uiDrawTextStretch, DWRITE_FONT_STRETCH> dwriteStretches;

View File

@ -28,14 +28,14 @@ struct uiDrawTextLayout {
#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) #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) // 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<uiDrawTextItalic, DWRITE_FONT_STYLE> dwriteItalics = { std::map<uiDrawTextItalic, DWRITE_FONT_STYLE> dwriteItalics = {
{ uiDrawTextItalicNormal, DWRITE_FONT_STYLE_NORMAL }, { uiDrawTextItalicNormal, DWRITE_FONT_STYLE_NORMAL },
{ uiDrawTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE }, { uiDrawTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE },
{ uiDrawTextItalicItalic, DWRITE_FONT_STYLE_ITALIC }, { 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) // 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<uiDrawTextStretch, DWRITE_FONT_STRETCH> dwriteStretches = { std::map<uiDrawTextStretch, DWRITE_FONT_STRETCH> dwriteStretches = {
{ uiDrawTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED }, { uiDrawTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED },
{ uiDrawTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED }, { uiDrawTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED },
{ uiDrawTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED }, { uiDrawTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED },
@ -219,12 +219,10 @@ void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
uiFree(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_BRUSH_PROPERTIES props;
D2D1_COLOR_F color; D2D1_COLOR_F color;
ID2D1SolidColorBrush *brush;
HRESULT hr;
ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES)); ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
props.opacity = 1.0; props.opacity = 1.0;
@ -235,10 +233,18 @@ static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, doubl
color.g = g; color.g = g;
color.b = b; color.b = b;
color.a = a; color.a = a;
hr = rt->CreateSolidColorBrush( return rt->CreateSolidColorBrush(
&color, &color,
&props, &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) if (hr != S_OK)
logHRESULT(L"error creating solid brush", hr); logHRESULT(L"error creating solid brush", hr);
return brush; return brush;
@ -250,9 +256,9 @@ class textRenderer : public IDWriteTextRenderer {
ULONG refcount; ULONG refcount;
ID2D1RenderTarget *rt; ID2D1RenderTarget *rt;
BOOL snap; BOOL snap;
IUnknown *black; ID2D1SolidColorBrush *black;
public: public:
textRenderer(ID2D1RenderTarget *rt, BOOL snap, IUnknown *black) textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)
{ {
this->refcount = 1; this->refcount = 1;
this->rt = rt; 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) 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; D2D1_POINT_2F baseline;
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
ID2D1SolidColorBrush *brush;
baseline.x = baselineOriginX; baseline.x = baselineOriginX;
baseline.y = baselineOriginY; baseline.y = baselineOriginY;
if (clientDrawingEffect == NULL) brush = this->black;
clientDrawingEffect = 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( this->rt->DrawGlyphRun(
baseline, baseline,
glyphRun, glyphRun,
(ID2D1Brush *) clientDrawingEffect, brush,
measuringMode); measuringMode);
if (t != NULL && t->hasColor)
brush->Release();
return S_OK; return S_OK;
} }
@ -357,12 +373,16 @@ public:
virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect) virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)
{ {
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
// TODO // TODO
return S_OK; return S_OK;
} }
virtual HRESULT DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect) virtual HRESULT DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)
{ {
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
// TODO // TODO
return S_OK; return S_OK;
} }
@ -372,13 +392,13 @@ public:
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{ {
D2D1_POINT_2F pt; D2D1_POINT_2F pt;
ID2D1Brush *black; ID2D1SolidColorBrush *black;
textRenderer *renderer; textRenderer *renderer;
HRESULT hr; 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 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 // 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 renderD2D 0
#define renderOur 1 #define renderOur 1
@ -395,7 +415,7 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
// draw ours semitransparent so we can check // draw ours semitransparent so we can check
// TODO get the actual color Charles Petzold uses and use that // TODO get the actual color Charles Petzold uses and use that
black->Release(); 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 #endif
#if renderOur #if renderOur
renderer = new textRenderer(c->rt, renderer = new textRenderer(c->rt,

View File

@ -47,4 +47,5 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <functional>
#endif #endif