Created
March 14, 2018 02:01
-
-
Save andlabs/f9ff68157df05ba01c84b8d3ecb77bb8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 12 february 2017 | |
#include "uipriv_windows.hpp" | |
#include "attrstr.hpp" | |
// TODO this whole file needs cleanup | |
// we need to collect all the background blocks and add them all at once | |
// TODO contextual alternates override ligatures? | |
// TODO rename this struct to something that isn't exclusively foreach-ing? | |
struct foreachParams { | |
const uint16_t *s; | |
size_t len; | |
IDWriteTextLayout *layout; | |
std::vector<backgroundFunc> *backgroundFuncs; | |
}; | |
static std::hash<double> doubleHash; | |
// we need to combine color and underline style into one unit for IDWriteLayout::SetDrawingEffect() | |
// we also want to combine identical effects, which DirectWrite doesn't seem to provide a way to do | |
// we can at least try to goad it into doing so if we can deduplicate effects once they're all computed | |
// so what we do is use this class to store in-progress effects, much like uiprivCombinedFontAttr on the OS X code | |
// we then deduplicate them later while converting them into a form suitable for drawing with; see applyEffectsAttributes() below | |
class combinedEffectsAttr : public IUnknown { | |
ULONG refcount; | |
uiAttribute *colorAttr; | |
uiAttribute *underlineAttr; | |
uiAttribute *underlineColorAttr; | |
void setAttribute(uiAttribute *a) | |
{ | |
if (a == NULL) | |
return; | |
switch (uiAttributeGetType(a)) { | |
case uiAttributeTypeColor: | |
if (this->colorAttr != NULL) | |
uiprivAttributeRelease(this->colorAttr); | |
this->colorAttr = uiprivAttributeRetain(a); | |
break; | |
case uiAttributeTypeUnderline: | |
if (this->underlineAttr != NULL) | |
uiprivAttributeRelease(this->underlineAttr); | |
this->underlineAttr = uiprivAttributeRetain(a); | |
break; | |
case uiAttributeTypeUnderlineColor: | |
if (this->underlineAttr != NULL) | |
uiprivAttributeRelease(this->underlineAttr); | |
this->underlineColorAttr = uiprivAttributeRetain(a); | |
break; | |
} | |
} | |
// this is needed by applyEffectsAttributes() below | |
// TODO doesn't uiprivAttributeEqual() already do this; if it doesn't, make it so; if (or when) it does, fix all platforms to avoid this extra check | |
static bool attrEqual(uiAttribute *a, uiAttribute *b) const | |
{ | |
if (a == NULL && b == NULL) | |
return true; | |
if (a == NULL || b == NULL) | |
return false; | |
return uiprivAttributeEqual(a, b); | |
} | |
public: | |
combinedEffectsAttr(uiAttribute *a) | |
{ | |
this->refcount = 1; | |
this->colorAttr = NULL; | |
this->underlineAttr = NULL; | |
this->underlineColorAttr = NULL; | |
this->setAttribute(a); | |
} | |
~combinedEffectsAttr() | |
{ | |
if (this->colorAttr != NULL) | |
uiprivAttributeRelease(this->colorAttr); | |
if (this->underlineAttr != NULL) | |
uiprivAttributeRelease(this->underlineAttr); | |
if (this->underlineColorAttr != NULL) | |
uiprivAttributeRelease(this->underlineColorAttr); | |
} | |
// 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; | |
} | |
combinedEffectsAttr *cloneWith(uiAttribute *a) | |
{ | |
combinedEffectsAttr *b; | |
b = new combinedEffectsAttr(this->colorAttr); | |
b->setAttribute(this->underlineAttr); | |
b->setAttribute(this->underlineColorAttr); | |
b->setAttribute(a); | |
return b; | |
} | |
// and these are also needed by applyEffectsAttributes() below | |
size_t hash(void) const noexcept | |
{ | |
size_t ret = 0; | |
double r, g, b, a; | |
if (self->colorAttr != NULL) { | |
uiAttributeColor(self->colorAttr, &r, &g, &b, &a); | |
ret ^= doubleHash(r); | |
ret ^= doubleHash(g); | |
ret ^= doubleHash(b); | |
ret ^= doubleHash(a); | |
} | |
if (self->underlineAttr != NULL) | |
ret ^= (size_t) uiAttributeUnderline(self->underlineAttr); | |
if (self->underlineColorAttr != NULL) { | |
uiAttributeUnderlineColor(self->underlineColorAttr, &colorType, &r, &g, &b, &a); | |
ret ^= (size_t) colorType; | |
ret ^= doubleHash(r); | |
ret ^= doubleHash(g); | |
ret ^= doubleHash(b); | |
ret ^= doubleHash(a); | |
} | |
return ret; | |
} | |
bool equals(const combinedEffectsAttr *b) const | |
{ | |
if (b == NULL) | |
return false; | |
return combinedEffectsAttr::attrEqual(a->colorAttr, b->colorAttr) && | |
combinedEffectsAttr::attrEqual(a->underilneAttr, b->underlineAttr) && | |
combinedEffectsAttr::attrEqual(a->underlineColorAttr, b->underlineColorAttr); | |
} | |
drawingEffectsAttr *toDrawingEffectsAttr(void) | |
{ | |
drawingEffectsAttr *dea; | |
double r, g, b, a; | |
uiUnderlineColor colorType; | |
dea = new drawingEffectsAttr; | |
if (self->colorAttr != NULL) { | |
uiAttributeColor(self->colorAttr, &r, &g, &b, &a); | |
dea->addColor(r, g, b, a); | |
} | |
if (self->underlineAttr != NULL) | |
dea->addUnderline(uiAttributeUnderline(self->underlineAttr)); | |
if (self->underlineColorAttr != NULL) { | |
uiAttributeUnderlineColor(self->underlineColor, &colorType, &r, &g, &b, &a); | |
// TODO see if Microsoft has any standard colors for these | |
switch (colorType) { | |
case uiUnderlineColorSpelling: | |
// TODO consider using the GtkTextView style property error-underline-color here if Microsoft has no preference | |
r = 1.0; | |
g = 0.0; | |
b = 0.0; | |
a = 1.0; | |
break; | |
case uiUnderlineColorGrammar: | |
r = 0.0; | |
g = 1.0; | |
b = 0.0; | |
a = 1.0; | |
break; | |
case uiUnderlineColorAlternate: | |
r = 0.0; | |
g = 0.0; | |
b = 1.0; | |
a = 1.0; | |
break; | |
} | |
dea->addUnderlineColor(r, g, b, a); | |
} | |
return dea; | |
} | |
}; | |
// also needed by applyEffectsAttributes() below | |
class applyEffectsHash { | |
public: | |
typedef combinedEffectsAttr *ceaptr; | |
size_t operator()(applyEffectsHash::ceaptr const &cea) const noexcept | |
{ | |
return cea->hash(); | |
} | |
}; | |
class applyEffectsEqualTo { | |
public: | |
typedef combinedEffectsAttr *ceaptr; | |
bool operator()(const applyEffectsEqualTo::ceaptr &a, const applyEffectsEqualTo::ceaptr &b) const | |
{ | |
return a->equals(b); | |
} | |
}; | |
static HRESULT addEffectAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr) | |
{ | |
IUnknown *u; | |
combinedEffectsAttr *cea; | |
DWRITE_TEXT_RANGE range; | |
size_t diff; | |
HRESULT hr; | |
while (start < end) { | |
hr = p->layout->GetDrawingEffect(start, &u, &range); | |
if (hr != S_OK) | |
{logHRESULT(L"HELP", hr); | |
// TODO proper cleanup somehow | |
return hr; | |
} cea = (combinedEffectsAttr *) u; | |
if (cea == NULL) | |
cea = new combinedEffectsAttr(attr); | |
else | |
cea = cea->cloneWith(attr); | |
// clamp range within [start, end) | |
if (range.startPosition < start) { | |
diff = start - range.startPosition; | |
range.startPosition = start; | |
range.length -= diff; | |
} | |
if ((range.startPosition + range.length) > end) | |
range.length = end - range.startPosition; | |
hr = p->layout->SetDrawingEffect(cea, range); | |
if (hr != S_OK) | |
// TODO proper cleanup somehow | |
return hr; | |
// TODO figure out what and when needs to be released | |
start += range.length; | |
} | |
return S_OK; | |
} | |
static backgroundFunc mkBackgroundFunc(size_t start, size_t end, double r, double g, double b, double a) | |
{ | |
return [=](uiDrawContext *c, uiDrawTextLayout *layout, double x, double y) { | |
uiDrawBrush brush; | |
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 uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data) | |
{ | |
struct foreachParams *p = (struct foreachParams *) data; | |
DWRITE_TEXT_RANGE range; | |
WCHAR *wfamily; | |
size_t ostart, oend; | |
BOOL hasUnderline; | |
IDWriteTypography *dt; | |
HRESULT hr; | |
ostart = start; | |
oend = end; | |
// TODO fix const correctness | |
start = attrstrUTF8ToUTF16((uiAttributedString *) s, start); | |
end = attrstrUTF8ToUTF16((uiAttributedString *) s, end); | |
range.startPosition = start; | |
range.length = end - start; | |
switch (uiAttributeGetType(attr)) { | |
case uiAttributeTypeFamily: | |
wfamily = toUTF16(uiAttributeFamily(attr)); | |
hr = p->layout->SetFontFamilyName(wfamily, range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying family name attribute", hr); | |
uiFree(wfamily); | |
break; | |
case uiAttributeTypeSize: | |
hr = p->layout->SetFontSize( | |
// TODO unify with fontmatch.cpp and/or attrstr.hpp | |
#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) | |
pointSizeToDWriteSize(uiAttributeSize(attr)), | |
range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying size attribute", hr); | |
break; | |
case uiAttributeTypeWeight: | |
hr = p->layout->SetFontWeight( | |
uiprivWeightToDWriteWeight(uiAttributeWeight(attr)), | |
range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying weight attribute", hr); | |
break; | |
case uiAttributeTypeItalic: | |
hr = p->layout->SetFontStyle( | |
uiprivItalicToDWriteStyle(uiAttributeItalic(attr)), | |
range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying italic attribute", hr); | |
break; | |
case uiAttributeTypeStretch: | |
hr = p->layout->SetFontStretch( | |
uiprivStretchToDWriteStretch(uiAttributeStretch(attr)), | |
range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying stretch attribute", hr); | |
break; | |
case uiAttributeTypeUnderline: | |
// mark that we have an underline; otherwise, DirectWrite will never call our custom renderer's DrawUnderline() method | |
hasUnderline = FALSE; | |
if (uiAttributeUnderline(attr) != uiUnderlineNone) | |
hasUnderline = TRUE; | |
hr = p->layout->SetUnderline(hasUnderline, range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying underline attribute", hr); | |
// and fall through to set the underline style through the drawing effect | |
case uiAttributeTypeColor: | |
case uiAttributeTypeUnderlineColor: | |
hr = addEffectAttributeToRange(p, start, end, attr); | |
if (hr != S_OK) | |
logHRESULT(L"error applying effect (color, underline, or underline color) attribute", hr); | |
break; | |
case uiAttributeBackground: | |
p->backgroundFuncs->push_back( | |
mkBackgroundFunc(ostart, oend, | |
spec->R, spec->G, spec->B, spec->A)); | |
break; | |
case uiAttributeTypeFeatures: | |
// only generate an attribute if not NULL | |
// TODO do we still need to do this or not... | |
if (uiAttributeFeatures(attr) == NULL) | |
break; | |
dt = uiprivOpenTypeFeaturesToIDWriteTypography(uiAttributeFeatures(attr)); | |
hr = p->layout->SetTypography(dt, range); | |
if (hr != S_OK) | |
logHRESULT(L"error applying features attribute", hr); | |
dt->Release(); | |
break; | |
} | |
return uiForEachContinue; | |
} | |
static HRESULT applyEffectsAttributes(struct foreachParams *p) | |
{ | |
IUnknown *u; | |
combinedEffectsAttr *cea; | |
drawingEffectsAttr *dea; | |
DWRITE_TEXT_RANGE range; | |
// here's the magic: this std::unordered_map will deduplicate all of our combinedEffectsAttrs, mapping all identical ones to a single drawingEffectsAttr | |
// because drawingEffectsAttr is the *actual* drawing effect we want for rendering, we also replace the combinedEffectsAttrs with them in the IDWriteTextLayout at the same time | |
// note the use of our custom hash and equal_to implementations | |
std::unordered_map<combinedEffectsAttrs *, drawingEffectsAttr *, | |
applyEffectsHash, applyEffectsEqualTo> effects; | |
HRESULT hr; | |
// go through, replacing every combinedEffectsAttr with the proper drawingEffectsAttr | |
range.startPosition = 0; | |
while (range.startPosition < p->len) { | |
hr = p->layout->GetDrawingEffect(range.startPosition, &u, &range); | |
if (hr != S_OK) | |
// TODO proper cleanup somehow | |
return hr; | |
cea = (combinedEffectsAttr *) cea; | |
if (cea != NULL) { | |
auto diter = effects.find(cea); | |
if (diter != effects.end()) | |
dea = diter->second; | |
else { | |
dea = cea->toDrawingEffectsAttr(); | |
effects.insert(cea, dea); | |
} | |
hr = p->layout->SetDrawingEffect(dea, range); | |
if (hr != S_OK) | |
// TODO proper cleanup somehow | |
return hr; | |
} | |
range.startPosition += range.length; | |
} | |
// and clean up, finally destroying the combinedEffectAttrs too | |
#if 0 | |
TODO | |
for (auto iter = effects.begin(); iter != effects.end(); iter++) { | |
iter->first->Release(); | |
iter->second->Release(); | |
} | |
#endif | |
return S_OK; | |
} | |
void uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector<backgroundFunc> **backgroundFuncs) | |
{ | |
struct foreachParams fep; | |
HRESULT hr; | |
fep.s = attrstrUTF16(p->String); | |
fep.len = attrstrUTF16Len(p->String); | |
fep.layout = layout; | |
fep.backgroundFuncs = new std::vector<backgroundFunc>; | |
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); | |
hr = applyEffectsAttributes(&fep); | |
if (hr != S_OK) | |
logHRESULT(L"error applying effects attributes", hr); | |
*backgroundFuncs = fep.backgroundFuncs; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment