libui/windows/attrstr.cpp

428 lines
13 KiB
C++

// 12 february 2017
#include "uipriv_windows.hpp"
#include "attrstr.hpp"
// TODO this whole file needs cleanup
// yep, even when it supports C++11, it doesn't support C++11
// we require MSVC 2013; this was added in MSVC 2015 (https://msdn.microsoft.com/en-us/library/wfa0edys.aspx)
#ifdef _MSC_VER
#if _MSC_VER < 1900
#define noexcept
#endif
#endif
// we need to collect all the background parameters and add them all at once
// TODO consider having background parameters in the drawing effects
// 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<struct drawTextBackgroundParams *> *backgroundParams;
};
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)
{
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;
uiUnderlineColor colorType;
if (this->colorAttr != NULL) {
uiAttributeColor(this->colorAttr, &r, &g, &b, &a);
ret ^= doubleHash(r);
ret ^= doubleHash(g);
ret ^= doubleHash(b);
ret ^= doubleHash(a);
}
if (this->underlineAttr != NULL)
ret ^= (size_t) uiAttributeUnderline(this->underlineAttr);
if (this->underlineColorAttr != NULL) {
uiAttributeUnderlineColor(this->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(this->colorAttr, b->colorAttr) &&
combinedEffectsAttr::attrEqual(this->underlineAttr, b->underlineAttr) &&
combinedEffectsAttr::attrEqual(this->underlineColorAttr, b->underlineColorAttr);
}
drawingEffectsAttr *toDrawingEffectsAttr(void)
{
drawingEffectsAttr *dea;
double r, g, b, a;
uiUnderlineColor colorType;
dea = new drawingEffectsAttr;
if (this->colorAttr != NULL) {
uiAttributeColor(this->colorAttr, &r, &g, &b, &a);
dea->setColor(r, g, b, a);
}
if (this->underlineAttr != NULL)
dea->setUnderline(uiAttributeUnderline(this->underlineAttr));
if (this->underlineColorAttr != NULL) {
uiAttributeUnderlineColor(this->underlineColorAttr, &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 uiUnderlineColorAuxiliary:
r = 0.0;
g = 0.0;
b = 1.0;
a = 1.0;
break;
}
dea->setUnderlineColor(r, g, b, a);
}
return dea;
}
};
// also needed by applyEffectsAttributes() below
// TODO provide all the fields of std::hash and std::equal_to?
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)
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);
// SetDrawingEffect will AddRef(), so Release() our copy
// (and we're abandoning early if that failed, so this will make sure things are cleaned up in that case)
cea->Release();
if (hr != S_OK)
return hr;
start += range.length;
}
return S_OK;
}
static void addBackgroundParams(struct foreachParams *p, size_t start, size_t end, const uiAttribute *attr)
{
struct drawTextBackgroundParams *params;
params = uiprivNew(struct drawTextBackgroundParams);
params->start = start;
params->end = end;
uiAttributeColor(attr, &(params->r), &(params->g), &(params->b), &(params->a));
p->backgroundParams->push_back(params);
}
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;
BOOL hasUnderline;
IDWriteTypography *dt;
HRESULT hr;
start = uiprivAttributedStringUTF8ToUTF16(s, start);
end = uiprivAttributedStringUTF8ToUTF16(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);
uiprivFree(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:
// TODO const-correct this properly
hr = addEffectAttributeToRange(p, start, end, (uiAttribute *) attr);
if (hr != S_OK)
logHRESULT(L"error applying effect (color, underline, or underline color) attribute", hr);
break;
case uiAttributeTypeBackground:
addBackgroundParams(p, start, end, attr);
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<combinedEffectsAttr *, drawingEffectsAttr *,
applyEffectsHash, applyEffectsEqualTo> effects;
HRESULT hr;
// go through, replacing every combinedEffectsAttr with the proper drawingEffectsAttr
range.startPosition = 0;
// and in case this while loop never runs, make hr valid to start with
hr = S_OK;
while (range.startPosition < p->len) {
hr = p->layout->GetDrawingEffect(range.startPosition, &u, &range);
if (hr != S_OK)
// note that we are breaking instead of returning; this allows us to clean up on failure
break;
cea = (combinedEffectsAttr *) u;
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);
// don't release dea; we need the reference that's inside the map
// (we don't take extra references on lookup, so this will be fine)
if (hr != S_OK)
break;
}
range.startPosition += range.length;
}
// and clean up, finally destroying the combinedEffectAttrs too
// we do this in the case of failure as well, to make sure everything is properly cleaned up
for (auto iter = effects.begin(); iter != effects.end(); iter++) {
iter->first->Release();
iter->second->Release();
}
return hr;
}
void uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector<struct drawTextBackgroundParams *> **backgroundParams)
{
struct foreachParams fep;
HRESULT hr;
fep.s = uiprivAttributedStringUTF16String(p->String);
fep.len = uiprivAttributedStringUTF16Len(p->String);
fep.layout = layout;
fep.backgroundParams = new std::vector<struct drawTextBackgroundParams *>;
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
hr = applyEffectsAttributes(&fep);
if (hr != S_OK)
logHRESULT(L"error applying effects attributes", hr);
*backgroundParams = fep.backgroundParams;
}