From a826fd7516eddaf86b14159a33d238bcc1bbe54f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jan 2016 01:07:48 -0500 Subject: [PATCH] Wrote the text drawing code on Windows. Now to build and test. --- darwin/draw.m | 2 + windows/draw.c | 17 +++++ windows/drawtext.cpp | 149 ++++++++++++++++++++++++++++++++++++++- windows/uipriv_windows.h | 1 + 4 files changed, 167 insertions(+), 2 deletions(-) diff --git a/darwin/draw.m b/darwin/draw.m index defa5b81..33fc982c 100644 --- a/darwin/draw.m +++ b/darwin/draw.m @@ -509,6 +509,7 @@ struct uiDrawTextLayout { }; // TODO this is *really* iffy, but we need to know character offsets... +// TODO clean up the local variable names and improve documentation static intmax_t *strToCFStrOffsetList(const char *str, CFMutableStringRef *cfstr) { intmax_t *bytesToCharacters; @@ -768,6 +769,7 @@ void uiDrawFreeTextLayout(uiDrawTextLayout *layout) // Core Text doesn't draw onto a flipped view correctly; we have to do this // see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped) +// TODO how is this affected by the CTM? static void prepareContextForText(uiDrawContext *c, double *y) { CGContextSaveGState(c->c); diff --git a/windows/draw.c b/windows/draw.c index 3b90b56c..de9aa3fd 100644 --- a/windows/draw.c +++ b/windows/draw.c @@ -873,3 +873,20 @@ void uiDrawRestore(uiDrawContext *c) uiFree(state); ptrArrayDelete(c->states, c->states->len - 1); } + +// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms +void uiDrawText(uiDrawContext *c, doule x, double y, uiDrawTextLayout *layout) +{ + uiDrawBrush brush; + ID2D1Brush *black; + + ZeroMemory(&brush, sizeof (uiDrawBrush)); + brush.Type = uiDrawBrushTypeSolid; + brush.R = 0.0; + brush.G = 0.0; + brush.B = 0.0; + brush.A = 1.0; + black = makeBrush(&brush, c->rt); + doDrawText(c->rt, black, x, y, layout); + ID2D1Brush_Release(black); +} diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index a3b01edd..c5f9d7a5 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -120,7 +120,152 @@ double uiDrawPointsToTextSize(double points) return 0; } -void uiDrawText(uiDrawContext *c, double x, double y, const char *text, uiDrawTextStyle *style) +struct uiDrawTextLayout { + IDWriteTextFormat *format; + IDWriteTextLayout *layout; + intmax_t *bytesToCharacters; +}; + +#define MBTWC(str, n, wstr, bufsiz) MultiByteToWideChar(CP_UTF8, 0, str, n, wstr, bufsiz) + +// TODO figure out how ranges are specified in DirectWrite +// TODO clean up the local variable names and improve documentation +static intmax_t *toUTF16Offsets(const char *str, WCHAR **wstr, intmax_t *wlenout) { - // TODO + intmax_t *bytesToCharacters; + intmax_t i, len; + int wlen; + intmax_t outpos; + + len = strlen(str); + bytesToCharacters = (intmax_t *) uiAlloc(len * sizeof (intmax_t), "intmax_t[]"); + + wlen = MBTWC(str, -1, NULL, 0); + if (wlen == 0) + logLastError("error figuring out number of characters to convert to in toUTF16Offsets()"); + *wstr = (WCHAR *) uiAlloc(wlen * sizeof (WCHAR), "WCHAR[]"); + *wlenout = wlen; + + i = 0; + outpos = 0; + while (i < len) { + intmax_t n; + intmax_t j; + BOOL found; + + // figure out how many characters to convert and convert them + found = FALSE; + for (n = 1; (i + n - 1) < len; n++) + if (MBTWC(str + i, n, *wstr + outpos, wlen - outpos) == n) { + // found a full character + found = TRUE; + break; + } + // if this test passes we reached the end of the string without a successful conversion (invalid string) + if (!found) + logLastError("something bad happened when trying to prepare string in uiDrawNewTextLayout()"); + + // now save the character offsets for those bytes + for (j = 0; j < n; j++) + bytesToCharacters[j] = outpos; + + // and go to the next + outpos += n; + } + + return bytesToCharacters; +} + + +static const DWRITE_FONT_WEIGHT dwriteWeights[] = { + [uiDrawTextWeightThin] = DWRITE_FONT_WEIGHT_THIN, + [uiDrawTextWeightUltraLight] = DWRITE_FONT_WEIGHT_ULTRA_LIGHT, + [uiDrawTextWeightLight] = DWRITE_FONT_WEIGHT_LIGHT, + [uiDrawTextWeightBook] = DWRITE_FONT_WEIGHT_SEMI_LIGHT, + [uiDrawTextWeightNormal] = DWRITE_FONT_WEIGHT_NORMAL, + [uiDrawTextWeightMedium] = DWRITE_FONT_WEIGHT_MEDIUM, + [uiDrawTextWeightSemiBold] = DWRITE_FONT_WEIGHT_SEMI_BOLD, + [uiDrawTextWeightBold] = DWRITE_FONT_WEIGHT_BOLD, + [uiDrawTextWeightUtraBold] = DWRITE_FONT_WEIGHT_ULTRA_BOLD, + [uiDrawTextWeightHeavy] = DWRITE_FONT_WEIGHT_HEAVY, + [uiDrawTextWeightUltraHeavy] = DWRITE_FONT_WEIGHT_ULTRA_BLACK, +}; + +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, +}; + +uiDrawTextLayout *uiDrawNewTextLayout(const char *text, const uiDrawInitialTextStyle *initialStyle) +{ + uiDrawTextLayout *layout; + WCHAR *family; + WCHAR *wtext; + intmax_t wlen; + HRESULT hr; + + layout = uiNew(uiDrawTextLayout); + + family = toUTF16(initialStyle->Family); + hr = dwfactory->CreateTextFormat(family, + NULL, + dwriteWeights[initialStyle->Weight], + dwriteItalics[initialStyle->Italic], + dwriteStretches[initialStyle->Stretch], + // 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 + initialStyle->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("error creating IDWriteTextFormat in uiDrawNewTextLayout()", hr); + uiFree(family); + // TODO gravity + + layout->bytesToCharacters = toUTF16Offsets(text, &wtext, &wlen); + hr = dwfactory->CreateTextLayout(wstr, wlen, + layout->format, + // FLOAT is float, not double, so this should work... TODO + FLT_MAX, FLT_MAX, + &(layout->layout)); + if (hr != S_OK) + logHRESULT("error creating IDWriteTextLayout in uiDrawNewTextLayout()", hr); + uiFree(wtext); + + return layout; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *layout) +{ + layout->layout->Release(); + layout->format->Release(); + uiFree(layout); +} + +void doDrawText(ID2D1RenderTarget *rt, ID2D1Brush *black, double x, double y, uiDrawTextLayout *layout) +{ + D2D1_POINT_2F pt; + HRESULT hr; + + 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? + rt->DrawTextLayout(pt, layout->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE); } diff --git a/windows/uipriv_windows.h b/windows/uipriv_windows.h index 33ecb931..1ce3c8bd 100644 --- a/windows/uipriv_windows.h +++ b/windows/uipriv_windows.h @@ -138,6 +138,7 @@ extern void freeContext(uiDrawContext *); // drawtext.cpp extern HRESULT initDrawText(void); extern void uninitDrawText(void); +extern void doDrawText(ID2D1RenderTarget *rt, ID2D1Brush *black, double x, double y, uiDrawTextLayout *layout); #ifdef __cplusplus }