diff --git a/windows/_old_drawtext.cpp b/windows/_old_drawtext.cpp index 05a24f67..a10e1ad6 100644 --- a/windows/_old_drawtext.cpp +++ b/windows/_old_drawtext.cpp @@ -77,152 +77,8 @@ uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL cop 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, uiDrawTextWeightUltraBold, 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) { @@ -357,13 +213,7 @@ uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultF 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); @@ -417,18 +267,8 @@ 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]; @@ -452,48 +292,12 @@ IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt 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; diff --git a/windows/areaevents.cpp b/windows/areaevents.cpp index 7d391b85..615c06ea 100644 --- a/windows/areaevents.cpp +++ b/windows/areaevents.cpp @@ -2,6 +2,8 @@ #include "uipriv_windows.hpp" #include "area.hpp" +// TODO https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp notes on explicit RTL handling under MirrorXCoordinate(); also in areadraw.cpp too? + static uiModifiers getModifiers(void) { uiModifiers m = 0; diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp new file mode 100644 index 00000000..3932af52 --- /dev/null +++ b/windows/drawtext.cpp @@ -0,0 +1,244 @@ +// 17 january 2017 +#include "uipriv_windows.hpp" + +struct uiDrawTextLayout { + IDWriteTextFormat *format; + IDWriteTextLayout *layout; + UINT32 *nLines; + struct lineInfo *lineInfo; + // for converting DirectWrite indices to byte offsets + size_t *u16tou8; + size_t nu16tou8; // TODO I don't like the casing of this name; is it even necessary? +}; + +// 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 +#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) + +static const DWRITE_FONT_STYLE dwriteItalics[] = { + [uiDrawTextItalicNormal] = DWRITE_FONT_STYLE_NORMAL, + [uiDrawTextItalicOblique] = DWRITE_FONT_STYLE_OBLIQUE, + [uiDrawTextItalicItalic] = DWRITE_FONT_STYLE_ITALIC, +}; + +static const DWRITE_FONT_STRETCH dwriteStretches[] = { + [uiDrawTextStretchUltraCondensed] = DWRITE_FONT_STRETCH_ULTRA_CONDENSED, + [uiDrawTextStretchExtraCondensed] = DWRITE_FONT_STRETCH_EXTRA_CONDENSED, + [uiDrawTextStretchCondensed] = DWRITE_FONT_STRETCH_CONDENSED, + [uiDrawTextStretchSemiCondensed] = DWRITE_FONT_STRETCH_SEMI_CONDENSED, + [uiDrawTextStretchNormal] = DWRITE_FONT_STRETCH_NORMAL, + [uiDrawTextStretchSemiExpanded] = DWRITE_FONT_STRETCH_SEMI_EXPANDED, + [uiDrawTextStretchExpanded] = DWRITE_FONT_STRETCH_EXPANDED, + [uiDrawTextStretchExtraExpanded] = DWRITE_FONT_STRETCH_EXTRA_EXPANDED, + [uiDrawTextStretchUltraExpanded] = DWRITE_FONT_STRETCH_ULTRA_EXPANDED, +}; + +struct lineInfo { + size_t startPos; + size_t endPos; + size_t newlineCount; + double x; + double y; + double width; + double height; + double baseline; +}; + +// this function is deeply indebted to the PadWrite sample: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp +static void computeLineInfo(uiDrawTextLayout *tl) +{ + DWRITE_LINE_METRICS *dlm; + size_t nextStart; + UINT32 i; + DWRITE_HIT_TEST_METRICS htm; + UINT32 unused; + HRESULT hr; + + // TODO make sure this is legal; if not, switch to GetMetrics() and use its line count field instead + hr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines)); + switch (hr) { + case S_OK: + // TODO what do we do here + case E_NOT_SUFFICIENT_BUFFER: + break; + default: + logHRESULT(L"error getting number of lines in IDWriteTextLayout", hr); + } + tl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), "struct lineInfo[] (text layout)"); + + dlm = new DWRITE_LINE_METRICS[tl->nLines]; + // TODO make sure pasisng NULL here is legal + hr = tl->layout->GetLineMetrics(dlm, tl->nLines, NULL); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout line metrics", hr); + + // assume the first line starts at position 0 and the string flow is incremental + nextStart = 0; + for (i = 0; i < tl->nLines; i++) { + tl->lineinfo[i].startPos = nextStart; + tl->lineinfo[i].endPos = nextStart + dlm[i].length; + tl->lineinfo[i].newlineCount = dlm[i].newlineLength; + nextStart = tl->lineinfo[i].endpos; + + hr = layout->HitTestTextRange(line->startPos, (line->endPos - line->newlineCount) - line->startPos, + 0, 0, + &htm, 1, &unused); + if (hr == E_NOT_SUFFICIENT_BUFFER) + logHRESULT(L"TODO CONTACT ANDLABS — IDWriteTextLayout::HitTestTextRange() can return multiple ranges for a single line", hr); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout line rect", hr); + // TODO verify htm.textPosition and htm.length? + tl->lineInfo[i].x = htm.left; + tl->lineInfo[i].y = htm.top; + tl->lineInfo[i].width = htm.width; + tl->lineInfo[i].height = htm.height; + // TODO verify dlm[i].height == htm.height + + // TODO on Windows 8.1 and/or 10 we can use DWRITE_LINE_METRICS1 to get specific info about the ascent and descent; do we have an alternative? + // TODO and even on those platforms can we somehow split tyographic leading from spacing? + // TODO and on that note, can we have both line spacing proportionally above and uniformly below? + tl->lineinfo[i].baseline = dlm[i].baseline; + } + + delete[] dlm; +} + +uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width) +{ + uiDrawTextLayout *tl; + WCHAR *wDefaultFamily; + DWRITE_WORD_WRAPPING wrap; + FLOAT maxWidth; + HRESULT hr; + + tl = uiNew(uiDrawTextLayout); + + wDefaultFamily = toUTF16(defaultFont->Family); + hr = dwfactory->CreateTextFormat( + wDefaultFamily, NULL, + // for the most part, DirectWrite weights correlate to ours + // the differences: + // - Minimum — libui: 0, DirectWrite: 1 + // - Maximum — libui: 1000, DirectWrite: 999 + // TODO figure out what to do about this shorter range (the actual major values are the same (but with different names), so it's just a range issue) + (DWRITE_FONT_WEIGHT) (defaultFont->Weight), + dwriteItalics[defaultFont->Italic], + dwriteStrecthes[defaultFont->Stretch], + pointSizeToDWriteSize(defaultFont->Size), + // 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? + L"", + &(tl->format)); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextFormat", hr); + + hr = dwfactory->CreateTextLayout( + attrstrUTF16(s), attrstrUTF16Len(s), + tl->format, + // FLOAT is float, not double, so this should work... TODO + FLT_MAX, FLT_MAX, + &(tl->layout)); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextLayout", hr); + + // and set the width + // this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 (TODO verify this fact) (TODO this should be the default anyway) + wrap = DWRITE_WORD_WRAPPING_WRAP; + maxWidth = (FLOAT) width; + if (width < 0) { + // TODO is this wrapping juggling even necessary? + 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 = tl->layout->SetWordWrapping(wrap); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr); + hr = tl->layout->SetMaxWidth(maxWidth); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout max layout width", hr); + + computeLineInfo(tl); + + // and finally copy the UTF-16 to UTF-8 index conversion table + tl->u16tou8 = attrstrCopyUTF16ToUTF8(s, &(tl->nu16tou8)); + + // TODO can/should this be moved elsewhere? + uiFree(wDefaultFamily); + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + uiFree(tl->u16tou8); + uiFree(tl->lineInfo); + tl->layout->Release(); + tl->format->Release(); + uiFree(tl); +} + +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + D2D1_POINT_2F pt; + ID2D1Brush *black; + + // 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); + + 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 (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? + // TODO what is our pixel snapping setting related to the OPTIONS enum values? + c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE); + + black->Release(); +} + +// TODO for a single line the height includes the leading; should it? TextEdit on OS X always includes the leading and/or paragraph spacing, otherwise Klee won't work... +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + DWRITE_TEXT_METRICS metrics; + HRESULT hr; + + hr = tl->layout->GetMetrics(&metrics); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr); + *width = metrics.width; + // TODO make sure the behavior of this on empty strings is the same on all platforms (ideally should be 0-width, line height-height; TODO note this in the docs too) + *height = metrics.height; +} + +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return tl->nLines; +} + +// DirectWrite doesn't provide a direct way to do this, so we have to do this manually +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + *start = tl->lineInfo[line].startPos; + *start = tl->u16tou8[*start]; + *end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount; + *end = tl->u16tou8[*end]; +} + +void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +{ + // TODO +} + +void uiDrawTextLayoutByteIndexToGraphemeRect(uiDrawTextLayout *tl, size_t pos, int *line, double *x, double *y, double *width, double *height) +{ +} + +void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, uiDrawTextLayoutHitTestResult *result) +{ + // TODO +} + +void uiDrawTextLayoutByteRangeToRectangle(uiDrawTextLayout *tl, size_t start, size_t end, uiDrawTextLayoutByteRangeRectangle *r) +{ +}