diff --git a/windows/GNUfiles.mk b/windows/GNUfiles.mk index 886aea58..8ca7ad97 100644 --- a/windows/GNUfiles.mk +++ b/windows/GNUfiles.mk @@ -28,6 +28,7 @@ CXXFILES += \ windows/events.cpp \ windows/fontbutton.cpp \ windows/fontdialog.cpp \ + windows/grapheme.cpp \ windows/group.cpp \ windows/init.cpp \ windows/label.cpp \ @@ -66,9 +67,10 @@ RCFILES += \ # LONGTERM split into a separate file or put in GNUmakefile.libui somehow? # flags for the Windows API +# notice that usp10.lib comes before gdi32.lib # TODO prune this list LDFLAGS += \ - user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib comdlg32.lib d2d1.lib dwrite.lib ole32.lib oleaut32.lib oleacc.lib uuid.lib + user32.lib kernel32.lib usp10.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib comdlg32.lib d2d1.lib dwrite.lib ole32.lib oleaut32.lib oleacc.lib uuid.lib # flags for building a shared library LDFLAGS += \ diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index 7a03f65d..98bb745d 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -337,6 +337,7 @@ struct layoutAttr { struct uiDrawTextLayout { WCHAR *text; size_t textlen; + size_t *graphemes; double width; IDWriteTextFormat *format; std::vector *attrs; @@ -366,6 +367,7 @@ uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultF layout->text = toUTF16(text); layout->textlen = wcslen(layout->text); + layout->graphemes = graphemes(layout->text); uiDrawTextLayoutSetWidth(layout, width); @@ -425,8 +427,8 @@ IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt logHRESULT(L"error creating IDWriteTextLayout", hr); for (const struct layoutAttr &attr : *(layout->attrs)) { - range.startPosition = attr.start; - range.length = attr.end - attr.start; + 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 diff --git a/windows/grapheme.cpp b/windows/grapheme.cpp new file mode 100644 index 00000000..355e4037 --- /dev/null +++ b/windows/grapheme.cpp @@ -0,0 +1,80 @@ +// 25 may 2016 +#include "uipriv_windows.hpp" + +// We could use CharNext() to generate grapheme cluster boundaries, but it doesn't handle surrogate pairs properly (see http://archives.miloush.net/michkap/archive/2008/12/16/9223301.html). +// So let's use Uniscribe (see http://archives.miloush.net/michkap/archive/2005/01/14/352802.html) +// See also http://www.catch22.net/tuts/uniscribe-mysteries and http://www.catch22.net/tuts/keyboard-navigation for more details. + +static HRESULT itemize(WCHAR *msg, size_t len, SCRIPT_ITEM **out, int *outn) +{ + SCRIPT_CONTROL sc; + SCRIPT_STATE ss; + SCRIPT_ITEM *items; + size_t maxItems; + int n; + HRESULT hr; + + // make sure these are zero-initialized to avoid mangling the text + ZeroMemory(&sc, sizeof (SCRIPT_CONTROL)); + ZeroMemory(&ss, sizeof (SCRIPT_STATE)); + + maxItems = len + 2; + for (;;) { + items = new SCRIPT_ITEM[maxItems]; + hr = ScriptItemize(msg, len, + maxItems, + &sc, &ss, + items, &n); + if (hr == S_OK) + break; + // otherwise either an error or not enough room + delete[] items; + if (hr != E_OUTOFMEMORY) + return hr; + maxItems *= 2; // add some more and try again + } + + *out = items; + *outn = n; + return S_OK; +} + +size_t *graphemes(WCHAR *msg) +{ + size_t len; + SCRIPT_ITEM *items; + int i, n; + size_t *out; + size_t *op; + SCRIPT_LOGATTR *logattr; + int j, nn; + HRESULT hr; + + len = wcslen(msg); + hr = itemize(msg, len, &items, &n); + if (hr != S_OK) + logHRESULT(L"error itemizing string for finding grapheme cluster boundaries", hr); + + // should be enough; 2 more just to be safe + out = (size_t *) uiAlloc((len + 2) * sizeof (size_t), "size_t[]"); + op = out; + + // note that there are actually n + 1 elements in items + for (i = 0; i < n; i++) { + nn = items[i + 1].iCharPos - items[i].iCharPos; + logattr = new SCRIPT_LOGATTR[nn]; + hr = ScriptBreak(msg + items[i].iCharPos, nn, + &(items[i].a), logattr); + if (hr != S_OK) + logHRESULT(L"error breaking string for finding grapheme cluster boundaries", hr); + for (j = 0; j < nn; j++) + if (logattr[j].fCharStop != 0) + *op++ = items[i].iCharPos + j; + delete[] logattr; + } + // and handle the last item for the end of the string + *op++ = items[i].iCharPos; + + delete[] items; + return out; +} diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp index f08ae042..abf72943 100644 --- a/windows/uipriv_windows.hpp +++ b/windows/uipriv_windows.hpp @@ -140,6 +140,10 @@ extern BOOL showColorDialog(HWND parent, struct colorDialogRGBA *c); // sizing.cpp extern void getSizing(HWND hwnd, uiWindowsSizing *sizing, HFONT font); +// grapheme.cpp +extern size_t *graphemes(WCHAR *msg); + + diff --git a/windows/winapi.hpp b/windows/winapi.hpp index ed30b782..246760da 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include