[PATCH v7 0/1] MR10704: Display invalid Indic syllables
Use the U+25CC DOTTED CIRCLE as the base glyph for invalid Indic syllables https://bugs.winehq.org/show_bug.cgi?id=27637 -- v7: Display invalid Indic syllables https://gitlab.winehq.org/wine/wine/-/merge_requests/10704
From: Aric Stewart <aric@codeweavers.com> Use the U+25CC DOTTED CIRCLE as the base glyph for invalid Indic syllables Handle the resulting pwLogClust changes correctly. https://bugs.winehq.org/show_bug.cgi?id=27637 --- dlls/gdi32/uniscribe/indic.c | 47 ++++--- dlls/gdi32/uniscribe/shape.c | 191 +++++++++++++++++++++++--- dlls/gdi32/uniscribe/usp10_internal.h | 6 +- dlls/usp10/tests/usp10.c | 90 ++++++++++++ 4 files changed, 289 insertions(+), 45 deletions(-) diff --git a/dlls/gdi32/uniscribe/indic.c b/dlls/gdi32/uniscribe/indic.c index 2d527ddbd1a..13f6c48c702 100644 --- a/dlls/gdi32/uniscribe/indic.c +++ b/dlls/gdi32/uniscribe/indic.c @@ -326,6 +326,7 @@ void Indic_ParseSyllables(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, const unsigned int center = 0; int index = 0; int next = 0; + BOOL valid; *syllable_count = 0; @@ -344,30 +345,35 @@ void Indic_ParseSyllables(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, const if (next >= cChar) break; next = Indic_process_next_syllable(input, cChar, 0, ¢er, index, lex); - if (next != -1) - { - *syllables = realloc(*syllables, sizeof(IndicSyllable)*(*syllable_count+1)); - (*syllables)[*syllable_count].start = index; - (*syllables)[*syllable_count].base = center; - (*syllables)[*syllable_count].ralf = -1; - (*syllables)[*syllable_count].blwf = -1; - (*syllables)[*syllable_count].pref = -1; - (*syllables)[*syllable_count].end = next-1; - FindBaseConsonant(hdc, psa, psc, input, &(*syllables)[*syllable_count], lex, modern); - index = next; - *syllable_count = (*syllable_count)+1; - } - else if (index < cChar) - { + valid = (next != -1); + if (index < cChar && !valid) { TRACE("Processing failed at %i\n",index); - next = ++index; + center = index; + next = index + 1; } + *syllables = realloc(*syllables, sizeof(IndicSyllable)*(*syllable_count+1)); + if (!*syllables) { + ERR("Allocation failure of syllables\n"); + *syllable_count = 0; + return; + } + (*syllables)[*syllable_count].valid = valid; + (*syllables)[*syllable_count].start = index; + (*syllables)[*syllable_count].base = center; + (*syllables)[*syllable_count].ralf = -1; + (*syllables)[*syllable_count].blwf = -1; + (*syllables)[*syllable_count].pref = -1; + (*syllables)[*syllable_count].end = next-1; + if (valid) + FindBaseConsonant(hdc, psa, psc, input, &(*syllables)[*syllable_count], lex, modern); + index = next; + *syllable_count = (*syllable_count)+1; } TRACE("Processed %i of %i characters into %i syllables\n",index,cChar,*syllable_count); } -void Indic_ReorderCharacters(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, WCHAR *input, unsigned int cChar, - IndicSyllable **syllables, int *syllable_count, lexical_function lex, reorder_function reorder_f, BOOL modern) +void Indic_ReorderCharacters(WCHAR *input, IndicSyllable *syllables, int syllable_count, + lexical_function lex, reorder_function reorder_f) { int i; @@ -377,7 +383,6 @@ void Indic_ReorderCharacters(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, WC return; } - Indic_ParseSyllables(hdc, psa, psc, input, cChar, syllables, syllable_count, lex, modern); - for (i = 0; i < *syllable_count; i++) - reorder_f(input, &(*syllables)[i], lex); + for (i = 0; i < syllable_count; i++) + reorder_f(input, &syllables[i], lex); } diff --git a/dlls/gdi32/uniscribe/shape.c b/dlls/gdi32/uniscribe/shape.c index 9f67b99c11d..5df6de37203 100644 --- a/dlls/gdi32/uniscribe/shape.c +++ b/dlls/gdi32/uniscribe/shape.c @@ -38,6 +38,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(uniscribe); #define FIRST_ARABIC_CHAR 0x0600 #define LAST_ARABIC_CHAR 0x06ff +#define U_DOTTED_CIRCLE 0x25cc + typedef VOID (*ContextualShapingProc)(HDC, ScriptCache*, SCRIPT_ANALYSIS*, WCHAR*, INT, WORD*, INT*, INT, WORD*); @@ -816,7 +818,7 @@ static void UpdateClusters(int nextIndex, int changeCount, int write_dir, int ch } else { - for (i = target_index; i < chars && i >= 0; i += cluster_dir) + for (i = target_index + 1; i < chars && i >= 0; i += cluster_dir) pwLogClust[i] += changeCount; } } @@ -909,7 +911,7 @@ static void mark_invalid_combinations(HDC hdc, const WCHAR* pwcChars, INT cChars { CHAR *context_type; int i,g; - WCHAR invalid = 0x25cc; + WCHAR invalid = U_DOTTED_CIRCLE; WORD invalid_glyph; context_type = malloc(cChars); @@ -1672,7 +1674,21 @@ static void ReplaceInsertChars(HDC hdc, INT cWalk, INT* pcChars, WCHAR *pwOutCha } } -static void DecomposeVowels(HDC hdc, WCHAR *pwOutChars, INT *pcChars, const VowelComponents vowels[], WORD* pwLogClust, INT cChars) +static void UpdateSyllables(INT cWalk, INT count, IndicSyllable * syllables, INT syllable_count) { + int i; + if (count < 1) return; + for (i = 0; i < syllable_count; i++) { + if (syllables[i].start >= cWalk) { + if (syllables[i].start > cWalk) { + syllables[i].start += count; + } + syllables[i].end += count; + } + + } +} + +static void DecomposeVowels(HDC hdc, WCHAR *pwOutChars, INT *pcChars, const VowelComponents vowels[], WORD* pwLogClust, INT cChars, IndicSyllable *syllables, INT syllable_count) { int i; int cWalk; @@ -1684,10 +1700,12 @@ static void DecomposeVowels(HDC hdc, WCHAR *pwOutChars, INT *pcChars, const Vowe if (pwOutChars[cWalk] == vowels[i].base) { int o = 0; + int s = cWalk; ReplaceInsertChars(hdc, cWalk, pcChars, pwOutChars, vowels[i].parts); if (vowels[i].parts[1]) { cWalk++; o++; } if (vowels[i].parts[2]) { cWalk++; o++; } UpdateClusters(cWalk, o, 1, cChars, pwLogClust); + UpdateSyllables(s, o, syllables, syllable_count); break; } } @@ -1730,6 +1748,7 @@ static void ComposeConsonants(HDC hdc, WCHAR *pwOutChars, INT *pcChars, const Co static void Reorder_Ra_follows_base(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { + if (!s->valid) return; if (s->ralf >= 0) { int j; @@ -1749,6 +1768,7 @@ static void Reorder_Ra_follows_base(WCHAR *pwChar, IndicSyllable *s, lexical_fun static void Reorder_Ra_follows_matra(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { + if (!s->valid) return; if (s->ralf >= 0) { int j,loc; @@ -1774,6 +1794,7 @@ static void Reorder_Ra_follows_matra(WCHAR *pwChar, IndicSyllable *s, lexical_fu static void Reorder_Ra_follows_syllable(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { + if (!s->valid) return; if (s->ralf >= 0) { int j; @@ -1797,6 +1818,7 @@ static void Reorder_Matra_precede_base(WCHAR *pwChar, IndicSyllable *s, lexical_ { int i; + if (!s->valid) return; /* reorder Matras */ if (s->end > s->base) { @@ -1824,6 +1846,7 @@ static void Reorder_Matra_precede_syllable(WCHAR *pwChar, IndicSyllable *s, lexi { int i; + if (!s->valid) return; /* reorder Matras */ if (s->end > s->base) { @@ -1850,6 +1873,7 @@ static void Reorder_Matra_precede_syllable(WCHAR *pwChar, IndicSyllable *s, lexi static void SecondReorder_Blwf_follows_matra(const WCHAR *chars, const IndicSyllable *s, WORD *glyphs, const IndicSyllable *g, lexical_function lexical) { + if (!s->valid) return; if (s->blwf >= 0 && g->blwf > g->base) { int j,loc; @@ -1878,6 +1902,7 @@ static void SecondReorder_Matra_precede_base(const WCHAR *chars, const IndicSyll { int i; + if (!s->valid) return; /* reorder previously moved Matras to correct position*/ for (i = s->start; i < s->base; i++) { @@ -1900,6 +1925,7 @@ static void SecondReorder_Matra_precede_base(const WCHAR *chars, const IndicSyll static void SecondReorder_Pref_precede_base(const IndicSyllable *s, WORD *glyphs, const IndicSyllable *g, lexical_function lexical) { + if (!s->valid) return; if (s->pref >= 0 && g->pref > g->base) { int j; @@ -1914,6 +1940,7 @@ static void SecondReorder_Pref_precede_base(const IndicSyllable *s, static void Reorder_Like_Sinhala(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(pwChar[s->base]) == lex_Vowel) return; @@ -1924,6 +1951,7 @@ static void Reorder_Like_Sinhala(WCHAR *pwChar, IndicSyllable *s, lexical_functi static void Reorder_Like_Devanagari(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(pwChar[s->base]) == lex_Vowel) return; @@ -1934,6 +1962,7 @@ static void Reorder_Like_Devanagari(WCHAR *pwChar, IndicSyllable *s, lexical_fun static void Reorder_Like_Bengali(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(pwChar[s->base]) == lex_Vowel) return; @@ -1944,6 +1973,7 @@ static void Reorder_Like_Bengali(WCHAR *pwChar, IndicSyllable *s, lexical_functi static void Reorder_Like_Kannada(WCHAR *pwChar, IndicSyllable *s, lexical_function lexical) { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(pwChar[s->base]) == lex_Vowel) return; @@ -1956,6 +1986,7 @@ static void SecondReorder_Like_Telugu(const WCHAR *chars, const IndicSyllable *s { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); TRACE("Glyphs (%i..%i..%i)\n",g->start,g->base,g->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(chars[s->base]) == lex_Vowel) return; @@ -1967,6 +1998,7 @@ static void SecondReorder_Like_Tamil(const WCHAR *chars, const IndicSyllable *s, { TRACE("Syllable (%i..%i..%i)\n",s->start,s->base,s->end); TRACE("Glyphs (%i..%i..%i)\n",g->start,g->base,g->end); + if (!s->valid) return; if (s->start == s->base && s->base == s->end) return; if (lexical(chars[s->base]) == lex_Vowel) return; @@ -2209,6 +2241,67 @@ static void ShapeIndicSyllables(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, } } +static inline int _check_invalid_glyph(WORD* pwGlyphs, INT check, INT invalid_glyph, IndicSyllable* syllable, INT offset) +{ + return (!(pwGlyphs[check] == invalid_glyph && + check >= syllable->start + offset && + check <= syllable->end + offset)); +} + +static void mark_invalid_syllables(HDC hdc, const WCHAR* pwcChars, INT cChars, WORD *pwGlyphs, INT *pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust, IndicSyllable *syllables, int syllable_count, lexical_function lexical) +{ + int i; + WCHAR invalid = U_DOTTED_CIRCLE; + WORD invalid_glyph; + int offset = 0; + + if (!hdc || !pwcChars || !pwGlyphs || !pcGlyphs || !pwLogClust || !syllables || syllable_count <= 0) { + ERR("Invalid parameters in mark_invalid_syllables\n"); + return; + } + if (cChars <= 0 || cMaxGlyphs <= 0) { + ERR("Invalid size parameters\n"); + return; + } + + for (i = 0; i < syllable_count; i++) + if (!syllables[i].valid) break; + + if (i >= syllable_count) { + /* Everything valid */ + return; + } + + if (NtGdiGetGlyphIndicesW(hdc, &invalid, 1, &invalid_glyph, 0) == GDI_ERROR || invalid_glyph == 0x0000) { + TRACE("Invalid glyph U_DOTTED_CIRCLE not found in font, using placeholder\n"); + invalid_glyph = 0x0020; // Use space as fallback + } + + /* Mark invalid combinations */ + for (i = 0; i < syllable_count; i++) + { + if (!syllables[i].valid) { + if (*pcGlyphs + 1 > cMaxGlyphs) { + ERR("Number of glyphs exceed buffer(%i, %i)\n", *pcGlyphs, cMaxGlyphs); + return; + } else { + int dir = (lexical(pwcChars[syllables[i].start]) == lex_Matra_pre)?1:0; + int index = syllables[i].start+dir+offset; + int j; + if (_check_invalid_glyph(pwGlyphs, index-1, invalid_glyph, &syllables[i], offset)) { + for (j = *pcGlyphs; j>=index; j--) + pwGlyphs[j+1] = pwGlyphs[j]; + pwGlyphs[index] = invalid_glyph; + *pcGlyphs = *pcGlyphs+1; + offset++; + } + for (j = cChars; j>syllables[i].base; j--) + pwLogClust[j] = pwLogClust[j] + 1; + } + } + } +} + static inline int unicode_lex(WCHAR c) { int type; @@ -2302,13 +2395,16 @@ static void ContextualShape_Sinhala(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS * memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, sinhala_lex, TRUE); + /* Step 1: Decompose multi part vowels */ - DecomposeVowels(hdc, input, &cCount, Sinhala_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Sinhala_vowels, pwLogClust, cChars, syllables, syllable_count); TRACE("New double vowel expanded string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, sinhala_lex, Reorder_Like_Sinhala, TRUE); + Indic_ReorderCharacters(input, syllables, syllable_count, sinhala_lex, Reorder_Like_Sinhala); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); /* Step 3: Strip dangling joiners */ @@ -2322,6 +2418,9 @@ static void ContextualShape_Sinhala(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS * /* Step 4: Base Form application to syllables */ NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, sinhala_lex); + ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, sinhala_lex, NULL, TRUE); free(input); @@ -2369,16 +2468,21 @@ static void ContextualShape_Devanagari(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSI input = malloc(cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, devanagari_lex, modern); + /* Step 1: Compose Consonant and Nukta */ ComposeConsonants(hdc, input, &cCount, Devanagari_consonants, pwLogClust); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, devanagari_lex, Reorder_Like_Devanagari, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, devanagari_lex, Reorder_Like_Devanagari); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, devanagari_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, devanagari_lex, NULL, modern); @@ -2425,17 +2529,22 @@ static void ContextualShape_Bengali(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS * input = malloc(2 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, bengali_lex, modern); + /* Step 1: Decompose Vowels and Compose Consonants */ - DecomposeVowels(hdc, input, &cCount, Bengali_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Bengali_vowels, pwLogClust, cChars, syllables, syllable_count); ComposeConsonants(hdc, input, &cCount, Bengali_consonants, pwLogClust); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, bengali_lex, Reorder_Like_Bengali, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, bengali_lex, Reorder_Like_Bengali); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, bengali_lex); + /* Step 3: Initial form is only applied to the beginning of words */ for (cCount = cCount - 1 ; cCount >= 0; cCount --) { @@ -2489,16 +2598,21 @@ static void ContextualShape_Gurmukhi(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS input = malloc(cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, gurmukhi_lex, modern); + /* Step 1: Compose Consonants */ ComposeConsonants(hdc, input, &cCount, Gurmukhi_consonants, pwLogClust); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, gurmukhi_lex, Reorder_Like_Bengali, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, gurmukhi_lex, Reorder_Like_Bengali); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, gurmukhi_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, gurmukhi_lex, NULL, modern); @@ -2533,12 +2647,17 @@ static void ContextualShape_Gujarati(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS input = malloc(cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, gujarati_lex, modern); + /* Step 1: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, gujarati_lex, Reorder_Like_Devanagari, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, gujarati_lex, Reorder_Like_Devanagari); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, gujarati_lex); + /* Step 2: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, gujarati_lex, NULL, modern); @@ -2584,17 +2703,22 @@ static void ContextualShape_Oriya(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *ps input = malloc(2 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, oriya_lex, modern); + /* Step 1: Decompose Vowels and Compose Consonants */ - DecomposeVowels(hdc, input, &cCount, Oriya_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Oriya_vowels, pwLogClust, cChars, syllables, syllable_count); ComposeConsonants(hdc, input, &cCount, Oriya_consonants, pwLogClust); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, oriya_lex, Reorder_Like_Bengali, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, oriya_lex, Reorder_Like_Bengali); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, oriya_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, oriya_lex, NULL, modern); @@ -2634,17 +2758,22 @@ static void ContextualShape_Tamil(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *ps input = malloc(2 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, tamil_lex, modern); + /* Step 1: Decompose Vowels and Compose Consonants */ - DecomposeVowels(hdc, input, &cCount, Tamil_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Tamil_vowels, pwLogClust, cChars, syllables, syllable_count); ComposeConsonants(hdc, input, &cCount, Tamil_consonants, pwLogClust); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, tamil_lex, Reorder_Like_Bengali, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, tamil_lex, Reorder_Like_Bengali); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, tamil_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, tamil_lex, SecondReorder_Like_Tamil, modern); @@ -2684,16 +2813,21 @@ static void ContextualShape_Telugu(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *p input = malloc(2 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, telugu_lex, modern); + /* Step 1: Decompose Vowels */ - DecomposeVowels(hdc, input, &cCount, Telugu_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Telugu_vowels, pwLogClust, cChars, syllables, syllable_count); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, telugu_lex, Reorder_Like_Bengali, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, telugu_lex, Reorder_Like_Bengali); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, telugu_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, telugu_lex, SecondReorder_Like_Telugu, modern); @@ -2736,16 +2870,21 @@ static void ContextualShape_Kannada(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS * input = malloc(3 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, kannada_lex, modern); + /* Step 1: Decompose Vowels */ - DecomposeVowels(hdc, input, &cCount, Kannada_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Kannada_vowels, pwLogClust, cChars, syllables, syllable_count); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, kannada_lex, Reorder_Like_Kannada, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, kannada_lex, Reorder_Like_Kannada); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, kannada_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, kannada_lex, SecondReorder_Like_Telugu, modern); @@ -2781,16 +2920,21 @@ static void ContextualShape_Malayalam(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS input = malloc(2 * cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, malayalam_lex, modern); + /* Step 1: Decompose Vowels */ - DecomposeVowels(hdc, input, &cCount, Malayalam_vowels, pwLogClust, cChars); + DecomposeVowels(hdc, input, &cCount, Malayalam_vowels, pwLogClust, cChars, syllables, syllable_count); TRACE("New composed string %s (%i)\n",debugstr_wn(input,cCount),cCount); /* Step 2: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, malayalam_lex, Reorder_Like_Devanagari, modern); + Indic_ReorderCharacters(input, syllables, syllable_count, malayalam_lex, Reorder_Like_Devanagari); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, malayalam_lex); + /* Step 3: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, malayalam_lex, SecondReorder_Like_Tamil, modern); @@ -2819,12 +2963,17 @@ static void ContextualShape_Khmer(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *ps input = malloc(cChars * sizeof(*input)); memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + /* Step 0: Syllables */ + Indic_ParseSyllables(hdc, psa, psc, input, cCount, &syllables, &syllable_count, khmer_lex, FALSE); + /* Step 1: Reorder within Syllables */ - Indic_ReorderCharacters( hdc, psa, psc, input, cCount, &syllables, &syllable_count, khmer_lex, Reorder_Like_Devanagari, FALSE); + Indic_ReorderCharacters(input, syllables, syllable_count, khmer_lex, Reorder_Like_Devanagari); TRACE("reordered string %s\n",debugstr_wn(input,cCount)); NtGdiGetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); *pcGlyphs = cCount; + mark_invalid_syllables(hdc, input, cChars, pwOutGlyphs, pcGlyphs, cMaxGlyphs, pwLogClust, syllables, syllable_count, khmer_lex); + /* Step 2: Base Form application to syllables */ ShapeIndicSyllables(hdc, psc, psa, input, cChars, syllables, syllable_count, pwOutGlyphs, pcGlyphs, pwLogClust, khmer_lex, NULL, FALSE); diff --git a/dlls/gdi32/uniscribe/usp10_internal.h b/dlls/gdi32/uniscribe/usp10_internal.h index b8ae1fb1a57..5ddbe75495c 100644 --- a/dlls/gdi32/uniscribe/usp10_internal.h +++ b/dlls/gdi32/uniscribe/usp10_internal.h @@ -218,6 +218,7 @@ typedef struct _scriptData } scriptData; typedef struct { + BOOL valid; INT start; INT base; INT ralf; @@ -284,9 +285,8 @@ HRESULT SHAPE_GetFontFeatureTags(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa OPENTYPE_TAG tagScript, OPENTYPE_TAG tagLangSys, int cMaxTags, OPENTYPE_TAG *pFeatureTags, int *pcTags); -void Indic_ReorderCharacters(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, WCHAR *input, - unsigned int cChars, IndicSyllable **syllables, int *syllable_count, - lexical_function lexical_f, reorder_function reorder_f, BOOL modern); +void Indic_ReorderCharacters(WCHAR *input, IndicSyllable *syllables, int syllable_count, + lexical_function lexical_f, reorder_function reorder_f); void Indic_ParseSyllables(HDC hdc, SCRIPT_ANALYSIS *psa, ScriptCache *psc, const WCHAR *input, unsigned int cChar, IndicSyllable **syllables, int *syllable_count, lexical_function lex, BOOL modern); diff --git a/dlls/usp10/tests/usp10.c b/dlls/usp10/tests/usp10.c index 5395768c0f8..28a0a704796 100644 --- a/dlls/usp10/tests/usp10.c +++ b/dlls/usp10/tests/usp10.c @@ -58,6 +58,17 @@ typedef struct _font_fingerprint { WORD result[10]; } font_fingerprint; +typedef struct _indic_test { + CHAR font[25]; + INT range; + WCHAR string[25]; + INT strlen; + INT item_count; + INT glyph_count; + INT glyphs[25]; + INT logClust[25]; +} indic_test; + static inline void _test_items_ok(LPCWSTR string, DWORD cchString, SCRIPT_CONTROL *Control, SCRIPT_STATE *State, DWORD nItems, const itemTest* items, BOOL nItemsToDo, @@ -4187,6 +4198,84 @@ static void test_script_cache_reuse(void) DestroyWindow(hwnd2); } +#define incomplete_indic(a,b) (winetest_set_location(__FILE__,__LINE__), 0) ? 0 : _incomplete_indic(a,b) + +static void _incomplete_indic(HDC hdc, const indic_test *test) +{ + HRESULT hr; + SCRIPT_CACHE sc = NULL; + WORD glyphs[10], logclust[10]; + SCRIPT_VISATTR attrs[10]; + SCRIPT_ITEM items[10]; + int nb, i; + HFONT font, oldfont = NULL; + + find_font_for_range(hdc, test->font, test->range, test->string[0], &font, &oldfont, NULL); + if (font != NULL) { + memset(items, 0, sizeof(items)); + nb = 0; + hr = ScriptItemize(test->string, test->strlen, test->strlen, NULL, NULL, items, &nb); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(items[0].a.eScript > 0, "Expected script id.\n"); + ok(nb == test->item_count, "Unexpected number of items.\n"); + + memset(glyphs, 0xff, sizeof(glyphs)); + nb = 0; + hr = ScriptShape(hdc, &sc, test->string, test->strlen, ARRAY_SIZE(glyphs), + &items[0].a, glyphs, logclust, attrs, &nb); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(nb == test->glyph_count, "Unexpected glyph count %u\n", nb); + + for (i = 0; i < nb; i++) { + ok(glyphs[i] == test->glyphs[i], "Unexpected glyph at %i: %x != %x\n",i, glyphs[i], test->glyphs[i]); + } + for (i = 0; i < test->strlen; i++) { + ok(logclust[i] == test->logClust[i], "Unexpected LogClust at %i: %x != %x\n",i, logclust[i], test->logClust[i]); + } + + ScriptFreeCache(&sc); + SelectObject(hdc, oldfont); + DeleteObject(font); + } else { + trace("%s font not found\n", test->font); + } +} + +static void test_incomplete_indic(HDC hdc) +{ + const indic_test test1 = { + "Mangal", 15, + {0x093f, 0x094b}, + 2, + 1, + 4, + {0x1d4, 0x29c, 0x29c, 0x22a}, + {0,2} + }; + const indic_test test2 = { + "Vrinda", 16, + {0x09CC,0x09CC,0x09CC}, + 3, + 1, + 9, + {0x13a,0xdd,0x11d,0x118,0xdd,0x11d,0x118,0xdd,0x11d}, + {0,3,6} + }; + const indic_test test3 = { + "Vrinda", 16, + {0x09C7,0x09D7,0x09C7,0x09D7}, + 4, + 1, + 8, + {0x13a, 0xdd, 0xdd, 0x11d, 0x118, 0xdd, 0xdd, 0x11d}, + {0,2,4,6} + }; + + incomplete_indic(hdc, &test1); + incomplete_indic(hdc, &test2); + incomplete_indic(hdc, &test3); +} + START_TEST(usp10) { HWND hwnd; @@ -4246,6 +4335,7 @@ START_TEST(usp10) test_ScriptIsComplex(); test_script_cache_reuse(); + test_incomplete_indic(hdc); ReleaseDC(hwnd, hdc); DestroyWindow(hwnd); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10704
Thanks for the review, Investigating the issue with the leading glyph vanishing revealed some other issues with LogClust processing. I have re-pushed with better logic and some testing. I looked closely at the behavior difference between `ৌৌ` `09cc 09cc` and `ৌৌ` `09c7 09d7 09c7 09d7` I believe things are working correctly now. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10704#note_139830
On Fri May 15 14:05:21 2026 +0000, Aric Stewart wrote:
Thanks for the review, Investigating the issue with the leading glyph vanishing revealed some other issues with LogClust processing. I have re-pushed with better logic and some testing. I looked closely at the behavior difference between `ৌৌ` `09cc 09cc` and `ৌৌ` `09c7 09d7 09c7 09d7` I believe things are working correctly now. Hi,
The `ৌ` issue indeed seems to be fixed. I have but uncovered another issue, could you look into that? When I type a devanagari base character (such as the consonant `क` and apply the anusvara `ं` and then the virama `्` , the virama correctly gets rendered as a separate glyph attached to a dotted circle. {width=51 height=44} But when I type ka + nuqta + anusvara + virama `क + ़ + ं + ्` = `क़ं्` the virama now attaches to the consonant ka instead of the dotted circle. {width=51 height=44} -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10704#note_140018
On Fri May 15 14:05:21 2026 +0000, समीरसिंह Sameer Singh wrote:
Hi, The `ৌ` issue indeed seems to be fixed. I have but uncovered another issue, could you look into that? When I type a devanagari base character (such as the consonant `क` and apply the anusvara `ं` and then the virama `्` , the virama correctly gets rendered as a separate glyph attached to a dotted circle. {width=51 height=44} But when I type ka + nuqta + anusvara + virama `क + ़ + ं + ्` = `क़ं्` the virama now attaches to the consonant ka instead of the dotted circle. {width=51 height=44} Interesting.. I will work to figure out what is going on there. Do you have a set of strings you are testing? I would love to flesh out the tests with a lot more cases, even ones that are working, that will help make sure that any changes I make do not break something else.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10704#note_140049
On Fri May 15 16:05:47 2026 +0000, Aric Stewart wrote:
Interesting.. I will work to figure out what is going on there. Do you have a set of strings you are testing? I would love to flesh out the tests with a lot more cases, even ones that are working, that will help make sure that any changes I make do not break something else. Unfortunately, I do not have a list of strings that I am testing the patch with. I type whatever comes to the mind. I may come up with some test cases later, but no guarantees on that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10704#note_140060
participants (3)
-
Aric Stewart -
Aric Stewart (@aricstewart) -
समीरसिंह Sameer Singh (@ss141309)