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 "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<size_t, textDrawingEffect *> *effects;
std::map<size_t, IDWriteTypography *> *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<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)
{
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<size_t, textDrawingEffect *>;
fep.features = new std::map<size_t, IDWriteTypography *>;
//TODO fep.backgroundClosures = g_ptr_array_new_with_free_func(unrefClosure);
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
applyAndFreeEffectsAttributes(&fep);
applyAndFreeFeatureAttributes(&fep);
//TODO *backgroundClosures = fep.backgroundClosures;
}

View File

@ -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<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))
// 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 },
{ 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<uiDrawTextStretch, DWRITE_FONT_STRETCH> dwriteStretches = {
std::map<uiDrawTextStretch, DWRITE_FONT_STRETCH> 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,

View File

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