diff --git a/dlls/usp10/Makefile.in b/dlls/usp10/Makefile.in index deccbc951d7..954ee3cce36 100644 --- a/dlls/usp10/Makefile.in +++ b/dlls/usp10/Makefile.in @@ -4,6 +4,7 @@ IMPORTS = gdi32 C_SRCS = \ bidi.c \ + indic.c \ mirror.c \ shape.c \ shaping.c \ diff --git a/dlls/usp10/indic.c b/dlls/usp10/indic.c new file mode 100644 index 00000000000..e734fef617a --- /dev/null +++ b/dlls/usp10/indic.c @@ -0,0 +1,270 @@ +/* + * Implementation of Indic Syllables for the Uniscribe Script Processor + * + * Copyright 2011 CodeWeavers, Aric Stewart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + */ +#include "config.h" +#include +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "wingdi.h" +#include "winnls.h" +#include "usp10.h" +#include "winternl.h" + +#include "wine/debug.h" +#include "usp10_internal.h" + +WINE_DEFAULT_DEBUG_CHANNEL(uniscribe); + +static void debug_output_string(LPCWSTR str, int cChar, lexical_function f) +{ + int i; + if (TRACE_ON(uniscribe)) + { + for (i = 0; i < cChar; i++) + { + switch (f(str[i])) + { + case lex_Consonant: TRACE("C"); break; + case lex_Ra: TRACE("Ra"); break; + case lex_Vowel: TRACE("V"); break; + case lex_Nukta: TRACE("N"); break; + case lex_Halant: TRACE("H"); break; + case lex_ZWNJ: TRACE("Zwnj"); break; + case lex_ZWJ: TRACE("Zwj"); break; + case lex_Mantra_post: TRACE("Mp");break; + case lex_Mantra_above: TRACE("Ma");break; + case lex_Mantra_below: TRACE("Mb");break; + case lex_Mantra_pre: TRACE("Mm");break; + case lex_Modifier: TRACE("Sm"); break; + case lex_Vedic: TRACE("Vd"); break; + case lex_Anudatta: TRACE("A"); break; + case lex_Composed_Vowel: TRACE("t"); break; + default: + TRACE("X"); break; + } + } + TRACE("\n"); + } +} + +static inline BOOL is_consonant( int type ) +{ + return (type == lex_Ra || type == lex_Consonant); +} + +static inline BOOL is_mantra( int type ) +{ + return (type == lex_Mantra_above || type == lex_Mantra_below || + type == lex_Mantra_pre || type == lex_Mantra_post); +} + +static inline BOOL is_joiner( int type ) +{ + return (type == lex_ZWJ || type == lex_ZWNJ); +} + +static INT consonant_header(LPCWSTR input, INT cChar, INT start, INT next, + lexical_function lex) +{ + if (!is_consonant( lex(input[next]) )) return -1; + next++; + if ((next < cChar) && lex(input[next]) == lex_Nukta) + next++; + if (lex(input[next])==lex_Halant) + { + next++; + if((next < cChar) && is_joiner( lex(input[next]) )) + next++; + if ((next < cChar) && is_consonant( lex(input[next]) )) + return next; + } + else if (is_joiner( lex(input[next]) ) && lex(input[next+1])==lex_Halant) + { + next+=2; + if ((next < cChar) && is_consonant( lex(input[next]) )) + return next; + } + return -1; +} + +static INT parse_consonant_syllable(LPCWSTR input, INT cChar, INT start, + INT *main, INT next, lexical_function lex) +{ + int check; + int headers = 0; + do + { + check = consonant_header(input,cChar,start,next,lex); + if (check != -1) + { + next = check; + headers++; + } + } while (check != -1); + if (headers || is_consonant( lex(input[next]) )) + { + *main = next; + next++; + } + else + return -1; + if ((next < cChar) && lex(input[next]) == lex_Nukta) + next++; + if ((next < cChar) && lex(input[next]) == lex_Anudatta) + next++; + + if ((next < cChar) && lex(input[next]) == lex_Halant) + { + next++; + if((next < cChar) && is_joiner( lex(input[next]) )) + next++; + } + else if (next < cChar) + { + while((next < cChar) && is_mantra( lex(input[next]) )) + next++; + if ((next < cChar) && lex(input[next]) == lex_Nukta) + next++; + if ((next < cChar) && lex(input[next]) == lex_Halant) + next++; + } + if ((next < cChar) && lex(input[next]) == lex_Modifier) + next++; + if ((next < cChar) && lex(input[next]) == lex_Vedic) + next++; + return next; +} + +static INT parse_vowel_syllable(LPCWSTR input, INT cChar, INT start, + INT next, lexical_function lex) +{ + if ((next < cChar) && lex(input[next]) == lex_Nukta) + next++; + if ((next < cChar) && is_joiner( lex(input[next]) ) && lex(input[next+1])==lex_Halant && is_consonant( lex(input[next+2]) )) + next+=3; + else if ((next < cChar) && lex(input[next])==lex_Halant && is_consonant( lex(input[next+1]) )) + next+=2; + else if ((next < cChar) && lex(input[next])==lex_ZWJ && is_consonant( lex(input[next+1]) )) + next+=2; + + if (is_mantra( lex(input[next]) )) + { + while((next < cChar) && is_mantra( lex(input[next]) )) + next++; + if ((next < cChar) && lex(input[next]) == lex_Nukta) + next++; + if ((next < cChar) && lex(input[next]) == lex_Halant) + next++; + } + + if ((next < cChar) && lex(input[next]) == lex_Modifier) + next++; + if ((next < cChar) && lex(input[next]) == lex_Vedic) + next++; + return next; +} + +static INT Indic_process_next_syllable( LPCWSTR input, INT cChar, INT start, INT* main, INT next, lexical_function lex ) +{ + if (lex(input[next])==lex_Vowel) + { + *main = next; + return parse_vowel_syllable(input, cChar, start, next+1, lex); + } + else if ((cChar > next+3) && lex(input[next]) == lex_Ra && lex(input[next+1]) == lex_Halant && lex(input[next+2]) == lex_Vowel) + { + *main = next+2; + return parse_vowel_syllable(input, cChar, start, next+3, lex); + } + + else if (start == next && lex(input[next])==lex_NBSP) + { + *main = next; + return parse_vowel_syllable(input, cChar, start, next+1, lex); + } + else if (start == next && (cChar > next+3) && lex(input[next]) == lex_Ra && lex(input[next+1]) == lex_Halant && lex(input[next+2]) == lex_NBSP) + { + *main = next+2; + return parse_vowel_syllable(input, cChar, start, next+3, lex); + } + + return parse_consonant_syllable(input, cChar, start, main, next, lex); +} + +void Indic_ReorderCharacters( LPWSTR input, int cChar, lexical_function lex, reorder_function reorder_f) +{ + int index = 0; + int next = 0; + int center = 0; + + if (!lex || ! reorder_f) + { + ERR("Failure to have required functions\n"); + return; + } + + debug_output_string(input, cChar, lex); + while (next != -1) + { + while((next < cChar) && lex(input[next]) == lex_Generic) + next++; + index = next; + next = Indic_process_next_syllable(input, cChar, 0, ¢er, index, lex); + if (next != -1) + { + reorder_f(input, index, center, next-1, lex); + index = next; + } + else if (index < cChar) + { + int i; + TRACE("Processing failed at %i\n",index); + for (i = index; i < cChar; i++) + if (lex(input[i])==lex_Generic) + { + TRACE("Restart processing at %i\n",i); + next = i; + index = i; + break; + } + } + } + TRACE("Processed %i of %i characters\n",index,cChar); +} + +int Indic_FindBaseConsonant(LPWSTR input, INT start, INT main, INT end, lexical_function lex) +{ + int i; + /* try to find a base consonant */ + if (!is_consonant( lex(input[main]) )) + { + for (i = end; i >= start; i--) + if (is_consonant( lex(input[i]) )) + { + main = i; + break; + } + } + return main; +} diff --git a/dlls/usp10/shape.c b/dlls/usp10/shape.c index 7f6b724876d..7687ceb2a80 100644 --- a/dlls/usp10/shape.c +++ b/dlls/usp10/shape.c @@ -43,6 +43,7 @@ typedef VOID (*ContextualShapingProc)(HDC, ScriptCache*, SCRIPT_ANALYSIS*, static void ContextualShape_Arabic(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WCHAR* pwcChars, INT cChars, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust); static void ContextualShape_Syriac(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WCHAR* pwcChars, INT cChars, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust); static void ContextualShape_Phags_pa(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WCHAR* pwcChars, INT cChars, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust); +static void ContextualShape_Sinhala(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WCHAR* pwcChars, INT cChars, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust); typedef VOID (*ShapeCharGlyphPropProc)( HDC , ScriptCache*, SCRIPT_ANALYSIS*, const WCHAR*, const INT, const WORD*, const INT, WORD*, SCRIPT_CHARPROP*, SCRIPT_GLYPHPROP*); @@ -279,6 +280,12 @@ typedef struct { static INT GSUB_apply_lookup(const GSUB_LookupList* lookup, INT lookup_index, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count); +typedef struct tagVowelComponents +{ + WCHAR base; + WCHAR parts[3]; +} VowelComponents; + /* the orders of joined_forms and contextual_features need to line up */ static const char* contextual_features[] = { @@ -400,7 +407,7 @@ static const ScriptShapeData ShapingData[] = {{ standard_features, 2}, NULL, "cyrl", "", NULL, NULL}, {{ standard_features, 2}, NULL, "armn", "", NULL, NULL}, {{ standard_features, 2}, NULL, "geor", "", NULL, NULL}, - {{ sinhala_features, 7}, NULL, "sinh", "", NULL, NULL}, + {{ sinhala_features, 7}, NULL, "sinh", "", ContextualShape_Sinhala, NULL}, {{ tibetan_features, 2}, NULL, "tibt", "", NULL, ShapeCharGlyphProp_Tibet}, {{ tibetan_features, 2}, NULL, "tibt", "", NULL, ShapeCharGlyphProp_Tibet}, {{ tibetan_features, 2}, NULL, "phag", "", ContextualShape_Phags_pa, ShapeCharGlyphProp_Thai}, @@ -1493,6 +1500,160 @@ static void ContextualShape_Phags_pa(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS HeapFree(GetProcessHeap(),0,context_shape); } +static void ReplaceInsertChars(HDC hdc, INT cWalk, INT* pcChars, WCHAR *pwOutChars, const WCHAR *replacements) +{ + int i; + + /* Replace */ + pwOutChars[cWalk] = replacements[0]; + cWalk=cWalk+1; + + /* Insert */ + for (i = 1; replacements[i] != 0x0000 && i < 3; i++) + { + int j; + for (j = *pcChars; j > cWalk; j--) + pwOutChars[j] = pwOutChars[j-1]; + *pcChars= *pcChars+1; + pwOutChars[cWalk] = replacements[i]; + cWalk = cWalk+1; + } +} + +static void DecomposeVowels(HDC hdc, WCHAR *pwOutChars, INT *pcChars, const VowelComponents vowels[]) +{ + int i; + int cWalk; + + for (cWalk = 0; cWalk < *pcChars; cWalk++) + { + for (i = 0; vowels[i].base != 0x0; i++) + { + if (pwOutChars[cWalk] == vowels[i].base) + { + ReplaceInsertChars(hdc, cWalk, pcChars, pwOutChars, vowels[i].parts); + break; + } + } + } +} + +static void Reorder_Ra_follows_base(LPWSTR pwChar, INT start, INT main, INT end, lexical_function lexical) +{ + if (start != main && end > start+1 && lexical(pwChar[start]) == lex_Ra && lexical(pwChar[start+1]) == lex_Halant) + { + int j; + WORD Ra = pwChar[start]; + WORD H = pwChar[start+1]; + + TRACE("Doing reorder of Ra to %i\n",main); + for (j = start; j < main-1; j++) + pwChar[j] = pwChar[j+2]; + pwChar[main-1] = Ra; + pwChar[main] = H; + } +} + +static void Reorder_Mantra_precede_base(LPWSTR pwChar, INT start, INT main, INT end, lexical_function lexical) +{ + int i; + + /* reorder Mantras */ + if (end > main) + { + for (i = 1; i <= end-main; i++) + { + if (lexical(pwChar[main+i]) == lex_Mantra_pre) + { + int j; + WCHAR c = pwChar[main+i]; + TRACE("Doing reorder of %x %x\n",c,pwChar[main]); + for (j = main+i; j > main; j--) + pwChar[j] = pwChar[j-1]; + pwChar[main] = c; + } + } + } +} + +static void Reorder_Like_Sinhala(LPWSTR pwChar, INT start, INT main, INT end, lexical_function lexical) +{ + TRACE("Syllable (%i..%i..%i)\n",start,main,end); + if (start == main && main == end) return; + + main = Indic_FindBaseConsonant(pwChar, start, main, end, lexical); + if (lexical(pwChar[main]) == lex_Vowel) return; + + Reorder_Ra_follows_base(pwChar, start, main, end, lexical); + Reorder_Mantra_precede_base(pwChar, start, main, end, lexical); +} + +static int sinhala_lex(WCHAR c) +{ + switch (c) + { + case 0x0DCA: return lex_Halant; + case 0x0DCF: + case 0x0DDF: + case 0x0DD8: return lex_Mantra_post; + case 0x0DD9: + case 0x0DDB: return lex_Mantra_pre; + case 0x0DDA: + case 0x0DDC: return lex_Composed_Vowel; + case 0x200D: return lex_ZWJ; + case 0x200C: return lex_ZWNJ; + case 0x00A0: return lex_NBSP; + default: + if (c>=0x0D82 && c <=0x0D83) return lex_Modifier; + else if (c>=0x0D85 && c <=0x0D96) return lex_Vowel; + else if (c>=0x0D96 && c <=0x0DC6) return lex_Consonant; + else if (c>=0x0DD0 && c <=0x0DD1) return lex_Mantra_post; + else if (c>=0x0DD2 && c <=0x0DD3) return lex_Mantra_above; + else if (c>=0x0DD4 && c <=0x0DD6) return lex_Mantra_below; + else if (c>=0x0DDD && c <=0x0DDE) return lex_Composed_Vowel; + else if (c>=0x0DF2 && c <=0x0DF3) return lex_Mantra_post; + else return lex_Generic; + } +} + +static const VowelComponents Sinhala_vowels[] = { + {0x0DDA, {0x0DD9,0x0DCA,0x0}}, + {0x0DDC, {0x0DD9,0x0DCF,0x0}}, + {0x0DDD, {0x0DD9,0x0DCF,0x0DCA}}, + {0x0DDE, {0x0DD9,0x0DDF,0x0}}, + {0x0000, {0x0000,0x0000,0x0}}}; + +static void ContextualShape_Sinhala(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WCHAR* pwcChars, INT cChars, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, WORD *pwLogClust) +{ + int cCount = cChars; + WCHAR *input; + + if (*pcGlyphs != cChars) + { + ERR("Number of Glyphs and Chars need to match at the beginning\n"); + return; + } + + input = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR) * (cChars * 3)); + + memcpy(input, pwcChars, cChars * sizeof(WCHAR)); + + /* Step 1: Decompose multi part vowels */ + DecomposeVowels(hdc, input, &cCount, Sinhala_vowels); + + TRACE("New double vowel expanded string %s (%i)\n",debugstr_wn(input,cCount),cCount); + + /* Step 2: Reorder within Syllables */ + Indic_ReorderCharacters( input, cCount, sinhala_lex, Reorder_Like_Sinhala); + TRACE("reordered string %s\n",debugstr_wn(input,cCount)); + + /* Step 3: Get glyphs */ + GetGlyphIndicesW(hdc, input, cCount, pwOutGlyphs, 0); + *pcGlyphs = cCount; + + HeapFree(GetProcessHeap(),0,input); +} + static void ShapeCharGlyphProp_Default( HDC hdc, ScriptCache* psc, SCRIPT_ANALYSIS* psa, const WCHAR* pwcChars, const INT cChars, const WORD* pwGlyphs, const INT cGlyphs, WORD* pwLogClust, SCRIPT_CHARPROP* pCharProp, SCRIPT_GLYPHPROP* pGlyphProp) { int i,k; diff --git a/dlls/usp10/usp10_internal.h b/dlls/usp10/usp10_internal.h index e68e247404f..0dc736ad0c8 100644 --- a/dlls/usp10/usp10_internal.h +++ b/dlls/usp10/usp10_internal.h @@ -74,6 +74,11 @@ typedef struct { OPENTYPE_TAG userLang; } ScriptCache; +enum {lex_Halant, lex_Composed_Vowel, lex_Mantra_post, lex_Mantra_pre, lex_Mantra_above, lex_Mantra_below, lex_ZWJ, lex_ZWNJ, lex_NBSP, lex_Modifier, lex_Vowel, lex_Consonant, lex_Generic, lex_Ra, lex_Vedic, lex_Anudatta, lex_Nukta}; + +typedef int (*lexical_function)(WCHAR c); +typedef void (*reorder_function)(LPWSTR pwChar, INT start, INT main, INT end, lexical_function lex); + #define odd(x) ((x) & 1) BOOL BIDI_DetermineLevels( LPCWSTR lpString, INT uCount, const SCRIPT_STATE *s, @@ -86,3 +91,6 @@ void SHAPE_ContextualShaping(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WC void SHAPE_ApplyDefaultOpentypeFeatures(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, WORD* pwOutGlyphs, INT* pcGlyphs, INT cMaxGlyphs, INT cChars, WORD *pwLogClust) DECLSPEC_HIDDEN; HRESULT SHAPE_CheckFontForRequiredFeatures(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa) DECLSPEC_HIDDEN; void SHAPE_CharGlyphProp(HDC hdc, ScriptCache *psc, SCRIPT_ANALYSIS *psa, const WCHAR* pwcChars, const INT cChars, const WORD* pwGlyphs, const INT cGlyphs, WORD *pwLogClust, SCRIPT_CHARPROP *pCharProp, SCRIPT_GLYPHPROP *pGlyphProp) DECLSPEC_HIDDEN; + +void Indic_ReorderCharacters( LPWSTR input, int cChars, lexical_function lexical_f, reorder_function reorder_f) DECLSPEC_HIDDEN; +int Indic_FindBaseConsonant(LPWSTR pwChar, INT start, INT main, INT end, lexical_function lex) DECLSPEC_HIDDEN;