// 3 december 2016 #include "../ui.h" #include "uipriv.h" #include "attrstr.h" struct uiAttributedString { char *s; size_t len; uiprivAttrList *attrs; // indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator // this ensures no one platform has a speed advantage (sorry GTK+) uint16_t *u16; size_t u16len; size_t *u8tou16; size_t *u16tou8; // this is lazily created to keep things from getting *too* slow uiprivGraphemes *graphemes; }; static void resize(uiAttributedString *s, size_t u8, size_t u16) { s->len = u8; s->s = (char *) uiprivRealloc(s->s, (s->len + 1) * sizeof (char), "char[] (uiAttributedString)"); s->u8tou16 = (size_t *) uiprivRealloc(s->u8tou16, (s->len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)"); s->u16len = u16; s->u16 = (uint16_t *) uiprivRealloc(s->u16, (s->u16len + 1) * sizeof (uint16_t), "uint16_t[] (uiAttributedString)"); s->u16tou8 = (size_t *) uiprivRealloc(s->u16tou8, (s->u16len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)"); } uiAttributedString *uiNewAttributedString(const char *initialString) { uiAttributedString *s; s = uiprivNew(uiAttributedString); s->attrs = uiprivNewAttrList(); uiAttributedStringAppendUnattributed(s, initialString); return s; } // TODO make sure that all implementations of uiprivNewGraphemes() work fine with empty strings; in particular, the Windows one might not static void recomputeGraphemes(uiAttributedString *s) { if (s->graphemes != NULL) return; if (uiprivGraphemesTakesUTF16()) { s->graphemes = uiprivNewGraphemes(s->u16, s->u16len); return; } s->graphemes = uiprivNewGraphemes(s->s, s->len); } static void invalidateGraphemes(uiAttributedString *s) { if (s->graphemes == NULL) return; uiprivFree(s->graphemes->pointsToGraphemes); uiprivFree(s->graphemes->graphemesToPoints); uiprivFree(s->graphemes); s->graphemes = NULL; } void uiFreeAttributedString(uiAttributedString *s) { uiprivFreeAttrList(s->attrs); invalidateGraphemes(s); uiprivFree(s->u16tou8); uiprivFree(s->u8tou16); uiprivFree(s->u16); uiprivFree(s->s); uiprivFree(s); } const char *uiAttributedStringString(const uiAttributedString *s) { return s->s; } size_t uiAttributedStringLen(const uiAttributedString *s) { return s->len; } static void u8u16len(const char *str, size_t *n8, size_t *n16) { uint32_t rune; char buf[4]; uint16_t buf16[2]; *n8 = 0; *n16 = 0; while (*str) { str = uiprivUTF8DecodeRune(str, 0, &rune); // TODO document the use of the function vs a pointer subtract here // TODO also we need to consider namespace collision with utf.h... *n8 += uiprivUTF8EncodeRune(rune, buf); *n16 += uiprivUTF16EncodeRune(rune, buf16); } } void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str) { uiAttributedStringInsertAtUnattributed(s, str, s->len); } // this works (and returns true, which is what we want) at s->len too because s->s[s->len] is always going to be 0 due to us allocating s->len + 1 bytes and because uiprivRealloc() always zero-fills allocated memory static int onCodepointBoundary(uiAttributedString *s, size_t at) { uint8_t c; // for uiNewAttributedString() if (s->s == NULL && at == 0) return 1; c = (uint8_t) (s->s[at]); return c < 0x80 || c >= 0xC0; } // TODO note that at must be on a codeoint boundary void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at) { uint32_t rune; char buf[4]; uint16_t buf16[2]; size_t n8, n16; // TODO make loop-local? to avoid using them in the wrong place again size_t old, old16; size_t oldn8, oldn16; size_t oldlen, old16len; size_t at16; size_t i; if (!onCodepointBoundary(s, at)) { // TODO } at16 = 0; if (s->u8tou16 != NULL) at16 = s->u8tou16[at]; // do this first to reclaim memory invalidateGraphemes(s); // first figure out how much we need to grow by // this includes post-validated UTF-8 u8u16len(str, &n8, &n16); // and resize old = at; old16 = at16; oldlen = s->len; old16len = s->u16len; resize(s, s->len + n8, s->u16len + n16); // move existing characters out of the way // note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912 memmove( s->s + at + n8, s->s + at, (oldlen - at) * sizeof (char)); memmove( s->u16 + at16 + n16, s->u16 + at16, (old16len - at16) * sizeof (uint16_t)); // note the + 1 for these; we want to copy the terminating null too memmove( s->u8tou16 + at + n8, s->u8tou16 + at, (oldlen - at + 1) * sizeof (size_t)); memmove( s->u16tou8 + at16 + n16, s->u16tou8 + at16, (old16len - at16 + 1) * sizeof (size_t)); oldn8 = n8; oldn16 = n16; // and copy while (*str) { size_t n; str = uiprivUTF8DecodeRune(str, 0, &rune); n = uiprivUTF8EncodeRune(rune, buf); n16 = uiprivUTF16EncodeRune(rune, buf16); s->s[old] = buf[0]; s->u8tou16[old] = old16; if (n > 1) { s->s[old + 1] = buf[1]; s->u8tou16[old + 1] = old16; } if (n > 2) { s->s[old + 2] = buf[2]; s->u8tou16[old + 2] = old16; } if (n > 3) { s->s[old + 3] = buf[3]; s->u8tou16[old + 3] = old16; } s->u16[old16] = buf16[0]; s->u16tou8[old16] = old; if (n16 > 1) { s->u16[old16 + 1] = buf16[1]; s->u16tou8[old16 + 1] = old; } old += n; old16 += n16; } // and have an index for the end of the string // TODO is this done by the below? //TODO s->u8tou16[old] = old16; //TODO s->u16tou8[old16] = old; // and adjust the prior values in the conversion tables // use <= so the terminating 0 gets updated too for (i = 0; i <= oldlen - at; i++) s->u8tou16[at + oldn8 + i] += s->u16len - old16len; for (i = 0; i <= old16len - at16; i++) s->u16tou8[at16 + oldn16 + i] += s->len - oldlen; // and finally do the attributes uiprivAttrListInsertCharactersUnattributed(s->attrs, at, n8); } // TODO document that end is the first index that will be maintained void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end) { size_t start16, end16; size_t count, count16; size_t i; if (!onCodepointBoundary(s, start)) { // TODO } if (!onCodepointBoundary(s, end)) { // TODO } count = end - start; start16 = s->u8tou16[start]; end16 = s->u8tou16[end]; count16 = end16 - start16; invalidateGraphemes(s); // overwrite old characters memmove( s->s + start, s->s + end, (s->len - end) * sizeof (char)); memmove( s->u16 + start16, s->u16 + end16, (s->u16len - end16) * sizeof (uint16_t)); // note the + 1 for these; we want to copy the terminating null too memmove( s->u8tou16 + start, s->u8tou16 + end, (s->len - end + 1) * sizeof (size_t)); memmove( s->u16tou8 + start16, s->u16tou8 + end16, (s->u16len - end16 + 1) * sizeof (size_t)); // update the conversion tables // note the use of <= to include the null terminator for (i = 0; i <= (s->len - end); i++) s->u8tou16[start + i] -= count16; for (i = 0; i <= (s->u16len - end16); i++) s->u16tou8[start16 + i] -= count; // null-terminate the string s->s[start + (s->len - end)] = 0; s->u16[start16 + (s->u16len - end16)] = 0; // fix up attributes uiprivAttrListRemoveCharacters(s->attrs, start, end); // and finally resize resize(s, s->len - count, s->u16len - count16); } void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end) { uiprivAttrListInsertAttribute(s->attrs, a, start, end); } // LONGTERM introduce an iterator object instead? void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data) { uiprivAttrListForEach(s->attrs, s, f, data); } // TODO figure out if we should count the grapheme past the end size_t uiAttributedStringNumGraphemes(uiAttributedString *s) { recomputeGraphemes(s); return s->graphemes->len; } size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos) { recomputeGraphemes(s); if (uiprivGraphemesTakesUTF16()) pos = s->u8tou16[pos]; return s->graphemes->pointsToGraphemes[pos]; } size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos) { recomputeGraphemes(s); pos = s->graphemes->graphemesToPoints[pos]; if (uiprivGraphemesTakesUTF16()) pos = s->u16tou8[pos]; return pos; } // helpers for platform-specific code const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s) { return s->u16; } size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s) { return s->u16len; } // TODO is this still needed given the below? size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n) { return s->u8tou16[n]; } size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n) { size_t *out; size_t nbytes; nbytes = (s->len + 1) * sizeof (size_t); *n = s->len; out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)"); memmove(out, s->u8tou16, nbytes); return out; } size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n) { size_t *out; size_t nbytes; nbytes = (s->u16len + 1) * sizeof (size_t); *n = s->u16len; out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)"); memmove(out, s->u16tou8, nbytes); return out; }