532 lines
15 KiB
C++
532 lines
15 KiB
C++
// 22 december 2015
|
|
#include "uipriv_windows.hpp"
|
|
#include "draw.hpp"
|
|
// TODO really migrate
|
|
|
|
// notes:
|
|
// only available in windows 8 and newer:
|
|
// - character spacing
|
|
// - kerning control
|
|
// - justficiation (how could I possibly be making this up?!)
|
|
// - vertical text (SERIOUSLY?! WHAT THE ACTUAL FUCK, MICROSOFT?!?!?!? DID YOU NOT THINK ABOUT THIS THE FIRST TIME, TRYING TO IMPROVE THE INTERNATIONALIZATION OF WINDOWS 7?!?!?! bonus: some parts of MSDN even say 8.1 and up only!)
|
|
|
|
struct uiDrawFontFamilies {
|
|
fontCollection *fc;
|
|
};
|
|
|
|
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
|
{
|
|
struct uiDrawFontFamilies *ff;
|
|
|
|
ff = uiNew(struct uiDrawFontFamilies);
|
|
ff->fc = loadFontCollection();
|
|
return ff;
|
|
}
|
|
|
|
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
|
{
|
|
return ff->fc->fonts->GetFontFamilyCount();
|
|
}
|
|
|
|
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
|
{
|
|
IDWriteFontFamily *family;
|
|
WCHAR *wname;
|
|
char *name;
|
|
HRESULT hr;
|
|
|
|
hr = ff->fc->fonts->GetFontFamily(n, &family);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error getting font out of collection", hr);
|
|
wname = fontCollectionFamilyName(ff->fc, family);
|
|
name = toUTF8(wname);
|
|
uiFree(wname);
|
|
family->Release();
|
|
return name;
|
|
}
|
|
|
|
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
|
{
|
|
fontCollectionFree(ff->fc);
|
|
uiFree(ff);
|
|
}
|
|
|
|
struct uiDrawTextFont {
|
|
IDWriteFont *f;
|
|
WCHAR *family; // save for convenience in uiDrawNewTextLayout()
|
|
double size;
|
|
};
|
|
|
|
uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size)
|
|
{
|
|
uiDrawTextFont *font;
|
|
WCHAR *copy;
|
|
HRESULT hr;
|
|
|
|
font = uiNew(uiDrawTextFont);
|
|
font->f = df;
|
|
if (addRef)
|
|
font->f->AddRef();
|
|
if (copyFamily) {
|
|
copy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), "WCHAR[]");
|
|
wcscpy(copy, family);
|
|
font->family = copy;
|
|
} else
|
|
font->family = family;
|
|
font->size = size;
|
|
return font;
|
|
}
|
|
|
|
// TODO consider moving these all to dwrite.cpp
|
|
|
|
// TODO MinGW-w64 is missing this one
|
|
#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT(350))
|
|
static const struct {
|
|
bool lastOne;
|
|
uiDrawTextWeight uival;
|
|
DWRITE_FONT_WEIGHT dwval;
|
|
} dwriteWeights[] = {
|
|
{ false, uiDrawTextWeightThin, DWRITE_FONT_WEIGHT_THIN },
|
|
{ false, uiDrawTextWeightUltraLight, DWRITE_FONT_WEIGHT_ULTRA_LIGHT },
|
|
{ false, uiDrawTextWeightLight, DWRITE_FONT_WEIGHT_LIGHT },
|
|
{ false, uiDrawTextWeightBook, DWRITE_FONT_WEIGHT_SEMI_LIGHT },
|
|
{ false, uiDrawTextWeightNormal, DWRITE_FONT_WEIGHT_NORMAL },
|
|
{ false, uiDrawTextWeightMedium, DWRITE_FONT_WEIGHT_MEDIUM },
|
|
{ false, uiDrawTextWeightSemiBold, DWRITE_FONT_WEIGHT_SEMI_BOLD },
|
|
{ false, uiDrawTextWeightBold, DWRITE_FONT_WEIGHT_BOLD },
|
|
{ false, uiDrawTextWeightUtraBold, DWRITE_FONT_WEIGHT_ULTRA_BOLD },
|
|
{ false, uiDrawTextWeightHeavy, DWRITE_FONT_WEIGHT_HEAVY },
|
|
{ true, uiDrawTextWeightUltraHeavy, DWRITE_FONT_WEIGHT_ULTRA_BLACK, },
|
|
};
|
|
|
|
static const struct {
|
|
bool lastOne;
|
|
uiDrawTextItalic uival;
|
|
DWRITE_FONT_STYLE dwval;
|
|
} dwriteItalics[] = {
|
|
{ false, uiDrawTextItalicNormal, DWRITE_FONT_STYLE_NORMAL },
|
|
{ false, uiDrawTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE },
|
|
{ true, uiDrawTextItalicItalic, DWRITE_FONT_STYLE_ITALIC },
|
|
};
|
|
|
|
static const struct {
|
|
bool lastOne;
|
|
uiDrawTextStretch uival;
|
|
DWRITE_FONT_STRETCH dwval;
|
|
} dwriteStretches[] = {
|
|
{ false, uiDrawTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED },
|
|
{ false, uiDrawTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED },
|
|
{ false, uiDrawTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED },
|
|
{ false, uiDrawTextStretchSemiCondensed, DWRITE_FONT_STRETCH_SEMI_CONDENSED },
|
|
{ false, uiDrawTextStretchNormal, DWRITE_FONT_STRETCH_NORMAL },
|
|
{ false, uiDrawTextStretchSemiExpanded, DWRITE_FONT_STRETCH_SEMI_EXPANDED },
|
|
{ false, uiDrawTextStretchExpanded, DWRITE_FONT_STRETCH_EXPANDED },
|
|
{ false, uiDrawTextStretchExtraExpanded, DWRITE_FONT_STRETCH_EXTRA_EXPANDED },
|
|
{ true, uiDrawTextStretchUltraExpanded, DWRITE_FONT_STRETCH_ULTRA_EXPANDED },
|
|
};
|
|
|
|
void attrToDWriteAttr(struct dwriteAttr *attr)
|
|
{
|
|
bool found;
|
|
int i;
|
|
|
|
found = false;
|
|
for (i = 0; ; i++) {
|
|
if (dwriteWeights[i].uival == attr->weight) {
|
|
attr->dweight = dwriteWeights[i].dwval;
|
|
found = true;
|
|
break;
|
|
}
|
|
if (dwriteWeights[i].lastOne)
|
|
break;
|
|
}
|
|
if (!found)
|
|
userbug("Invalid text weight %d passed to text function.", attr->weight);
|
|
|
|
found = false;
|
|
for (i = 0; ; i++) {
|
|
if (dwriteItalics[i].uival == attr->italic) {
|
|
attr->ditalic = dwriteItalics[i].dwval;
|
|
found = true;
|
|
break;
|
|
}
|
|
if (dwriteItalics[i].lastOne)
|
|
break;
|
|
}
|
|
if (!found)
|
|
userbug("Invalid text italic %d passed to text function.", attr->italic);
|
|
|
|
found = false;
|
|
for (i = 0; ; i++) {
|
|
if (dwriteStretches[i].uival == attr->stretch) {
|
|
attr->dstretch = dwriteStretches[i].dwval;
|
|
found = true;
|
|
break;
|
|
}
|
|
if (dwriteStretches[i].lastOne)
|
|
break;
|
|
}
|
|
if (!found)
|
|
// TODO on other platforms too
|
|
userbug("Invalid text stretch %d passed to text function.", attr->stretch);
|
|
}
|
|
|
|
void dwriteAttrToAttr(struct dwriteAttr *attr)
|
|
{
|
|
int weight, against, n;
|
|
int curdiff, curindex;
|
|
bool found;
|
|
int i;
|
|
|
|
// weight is scaled; we need to test to see what's nearest
|
|
weight = (int) (attr->dweight);
|
|
against = (int) (dwriteWeights[0].dwval);
|
|
curdiff = abs(against - weight);
|
|
curindex = 0;
|
|
for (i = 1; ; i++) {
|
|
against = (int) (dwriteWeights[i].dwval);
|
|
n = abs(against - weight);
|
|
if (n < curdiff) {
|
|
curdiff = n;
|
|
curindex = i;
|
|
}
|
|
if (dwriteWeights[i].lastOne)
|
|
break;
|
|
}
|
|
attr->weight = dwriteWeights[i].uival;
|
|
|
|
// italic and stretch are simple values; we can just do a matching search
|
|
found = false;
|
|
for (i = 0; ; i++) {
|
|
if (dwriteItalics[i].dwval == attr->ditalic) {
|
|
attr->italic = dwriteItalics[i].uival;
|
|
found = true;
|
|
break;
|
|
}
|
|
if (dwriteItalics[i].lastOne)
|
|
break;
|
|
}
|
|
if (!found)
|
|
// these are implbug()s because users shouldn't be able to get here directly; TODO?
|
|
implbug("invalid italic %d passed to dwriteAttrToAttr()", attr->ditalic);
|
|
|
|
found = false;
|
|
for (i = 0; ; i++) {
|
|
if (dwriteStretches[i].dwval == attr->dstretch) {
|
|
attr->stretch = dwriteStretches[i].uival;
|
|
found = true;
|
|
break;
|
|
}
|
|
if (dwriteStretches[i].lastOne)
|
|
break;
|
|
}
|
|
if (!found)
|
|
implbug("invalid stretch %d passed to dwriteAttrToAttr()", attr->dstretch);
|
|
}
|
|
|
|
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
|
{
|
|
uiDrawTextFont *font;
|
|
IDWriteFontCollection *collection;
|
|
UINT32 index;
|
|
BOOL exists;
|
|
struct dwriteAttr attr;
|
|
IDWriteFontFamily *family;
|
|
WCHAR *wfamily;
|
|
IDWriteFont *match;
|
|
HRESULT hr;
|
|
|
|
// always get the latest available font information
|
|
hr = dwfactory->GetSystemFontCollection(&collection, TRUE);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error getting system font collection", hr);
|
|
|
|
wfamily = toUTF16(desc->Family);
|
|
hr = collection->FindFamilyName(wfamily, &index, &exists);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error finding font family", hr);
|
|
if (!exists)
|
|
implbug("LONGTERM family not found in uiDrawLoadClosestFont()", hr);
|
|
hr = collection->GetFontFamily(index, &family);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error loading font family", hr);
|
|
|
|
attr.weight = desc->Weight;
|
|
attr.italic = desc->Italic;
|
|
attr.stretch = desc->Stretch;
|
|
attrToDWriteAttr(&attr);
|
|
hr = family->GetFirstMatchingFont(
|
|
attr.dweight,
|
|
attr.dstretch,
|
|
attr.ditalic,
|
|
&match);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error loading font", hr);
|
|
|
|
font = mkTextFont(match,
|
|
FALSE, // we own the initial reference; no need to add another one
|
|
wfamily, FALSE, // will be freed with font
|
|
desc->Size);
|
|
|
|
family->Release();
|
|
collection->Release();
|
|
|
|
return font;
|
|
}
|
|
|
|
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
|
{
|
|
font->f->Release();
|
|
uiFree(font->family);
|
|
uiFree(font);
|
|
}
|
|
|
|
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
|
{
|
|
return (uintptr_t) (font->f);
|
|
}
|
|
|
|
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
|
{
|
|
// TODO
|
|
|
|
desc->Size = font->size;
|
|
|
|
// TODO
|
|
}
|
|
|
|
// text sizes are 1/72 of an inch
|
|
// points in Direct2D are 1/96 of an inch (https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/hh447022%28v=vs.85%29.aspx)
|
|
// As for the actual conversion from design units, see:
|
|
// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html
|
|
// - https://sourceforge.net/p/vstgui/mailman/message/32483143/
|
|
// - http://xboxforums.create.msdn.com/forums/t/109445.aspx
|
|
// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx
|
|
// - http://www.fontbureau.com/blog/the-em/
|
|
// TODO make points here about how DIPs in DirectWrite == DIPs in Direct2D; if not, figure out what they really are? for the width and layout functions later
|
|
static double scaleUnits(double what, double designUnitsPerEm, double size)
|
|
{
|
|
return (what / designUnitsPerEm) * (size * (96.0 / 72.0));
|
|
}
|
|
|
|
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
|
{
|
|
DWRITE_FONT_METRICS dm;
|
|
|
|
font->f->GetMetrics(&dm);
|
|
metrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size);
|
|
metrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size);
|
|
// TODO what happens if dm.xxx is negative?
|
|
// TODO remember what this was for
|
|
metrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size);
|
|
metrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size);
|
|
metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size);
|
|
}
|
|
|
|
// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance
|
|
// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time
|
|
enum layoutAttrType {
|
|
layoutAttrColor,
|
|
};
|
|
|
|
struct layoutAttr {
|
|
enum layoutAttrType type;
|
|
int start;
|
|
int end;
|
|
double components[4];
|
|
};
|
|
|
|
struct uiDrawTextLayout {
|
|
WCHAR *text;
|
|
size_t textlen;
|
|
size_t *graphemes;
|
|
double width;
|
|
IDWriteTextFormat *format;
|
|
std::vector<struct layoutAttr> *attrs;
|
|
};
|
|
|
|
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
|
|
{
|
|
uiDrawTextLayout *layout;
|
|
HRESULT hr;
|
|
|
|
layout = uiNew(uiDrawTextLayout);
|
|
|
|
hr = dwfactory->CreateTextFormat(defaultFont->family,
|
|
NULL,
|
|
defaultFont->f->GetWeight(),
|
|
defaultFont->f->GetStyle(),
|
|
defaultFont->f->GetStretch(),
|
|
// typographic points are 1/72 inch; this parameter is 1/96 inch
|
|
// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx
|
|
defaultFont->size * (96.0 / 72.0),
|
|
// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx
|
|
// TODO use the current locale again?
|
|
L"",
|
|
&(layout->format));
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating IDWriteTextFormat", hr);
|
|
|
|
layout->text = toUTF16(text);
|
|
layout->textlen = wcslen(layout->text);
|
|
layout->graphemes = graphemes(layout->text);
|
|
|
|
uiDrawTextLayoutSetWidth(layout, width);
|
|
|
|
layout->attrs = new std::vector<struct layoutAttr>;
|
|
|
|
return layout;
|
|
}
|
|
|
|
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
|
{
|
|
delete layout->attrs;
|
|
layout->format->Release();
|
|
uiFree(layout->graphemes);
|
|
uiFree(layout->text);
|
|
uiFree(layout);
|
|
}
|
|
|
|
static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)
|
|
{
|
|
D2D1_BRUSH_PROPERTIES props;
|
|
D2D1_COLOR_F color;
|
|
ID2D1SolidColorBrush *brush;
|
|
HRESULT hr;
|
|
|
|
ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
props.opacity = 1.0;
|
|
// identity matrix
|
|
props.transform._11 = 1;
|
|
props.transform._22 = 1;
|
|
color.r = r;
|
|
color.g = g;
|
|
color.b = b;
|
|
color.a = a;
|
|
hr = rt->CreateSolidColorBrush(
|
|
&color,
|
|
&props,
|
|
&brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating solid brush", hr);
|
|
return brush;
|
|
}
|
|
|
|
IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt)
|
|
{
|
|
IDWriteTextLayout *dl;
|
|
DWRITE_TEXT_RANGE range;
|
|
IUnknown *unkBrush;
|
|
DWRITE_WORD_WRAPPING wrap;
|
|
FLOAT maxWidth;
|
|
HRESULT hr;
|
|
|
|
hr = dwfactory->CreateTextLayout(layout->text, layout->textlen,
|
|
layout->format,
|
|
// FLOAT is float, not double, so this should work... TODO
|
|
FLT_MAX, FLT_MAX,
|
|
&dl);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating IDWriteTextLayout", hr);
|
|
|
|
for (const struct layoutAttr &attr : *(layout->attrs)) {
|
|
range.startPosition = layout->graphemes[attr.start];
|
|
range.length = layout->graphemes[attr.end] - layout->graphemes[attr.start];
|
|
switch (attr.type) {
|
|
case layoutAttrColor:
|
|
if (rt == NULL) // determining extents, not drawing
|
|
break;
|
|
unkBrush = mkSolidBrush(rt,
|
|
attr.components[0],
|
|
attr.components[1],
|
|
attr.components[2],
|
|
attr.components[3]);
|
|
hr = dl->SetDrawingEffect(unkBrush, range);
|
|
unkBrush->Release(); // associated with dl
|
|
break;
|
|
default:
|
|
hr = E_FAIL;
|
|
logHRESULT(L"invalid text attribute type", hr);
|
|
}
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error adding attribute to text layout", hr);
|
|
}
|
|
|
|
// and set the width
|
|
// this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1
|
|
wrap = DWRITE_WORD_WRAPPING_WRAP;
|
|
maxWidth = layout->width;
|
|
if (layout->width < 0) {
|
|
wrap = DWRITE_WORD_WRAPPING_NO_WRAP;
|
|
// setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe
|
|
maxWidth = FLT_MAX; // see TODO above
|
|
}
|
|
hr = dl->SetWordWrapping(wrap);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error setting word wrapping mode", hr);
|
|
hr = dl->SetMaxWidth(maxWidth);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error setting max layout width", hr);
|
|
|
|
return dl;
|
|
}
|
|
|
|
|
|
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
|
|
{
|
|
layout->width = width;
|
|
}
|
|
|
|
// TODO for a single line the height includes the leading; it should not
|
|
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
|
|
{
|
|
IDWriteTextLayout *dl;
|
|
DWRITE_TEXT_METRICS metrics;
|
|
HRESULT hr;
|
|
|
|
dl = prepareLayout(layout, NULL);
|
|
hr = dl->GetMetrics(&metrics);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error getting layout metrics", hr);
|
|
*width = metrics.width;
|
|
// TODO make sure the behavior of this on empty strings is the same on all platforms
|
|
*height = metrics.height;
|
|
dl->Release();
|
|
}
|
|
|
|
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
|
{
|
|
IDWriteTextLayout *dl;
|
|
D2D1_POINT_2F pt;
|
|
ID2D1Brush *black;
|
|
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
|
|
black = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
|
|
|
|
dl = prepareLayout(layout, c->rt);
|
|
pt.x = x;
|
|
pt.y = y;
|
|
// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?
|
|
// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?
|
|
// TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?
|
|
c->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
|
|
dl->Release();
|
|
|
|
black->Release();
|
|
}
|
|
|
|
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
|
{
|
|
struct layoutAttr attr;
|
|
|
|
attr.type = layoutAttrColor;
|
|
attr.start = startChar;
|
|
attr.end = endChar;
|
|
attr.components[0] = r;
|
|
attr.components[1] = g;
|
|
attr.components[2] = b;
|
|
attr.components[3] = a;
|
|
layout->attrs->push_back(attr);
|
|
}
|