diff --git a/dlls/usp10/tests/usp10.c b/dlls/usp10/tests/usp10.c index 7e33eccfdea..3d19b0717bc 100644 --- a/dlls/usp10/tests/usp10.c +++ b/dlls/usp10/tests/usp10.c @@ -2438,6 +2438,70 @@ static void _test_item_ScriptXtoX(SCRIPT_ANALYSIS *psa, int cChars, int cGlyphs, } } +#define test_caret_item_ScriptXtoCP(a,b,c,d,e,f) _test_caret_item_ScriptXtoCP(__LINE__,a,b,c,d,e,f) + +static void _test_caret_item_ScriptXtoCP(int line, SCRIPT_ANALYSIS *psa, int cChars, int cGlyphs, const int* offsets, const WORD *pwLogClust, const int* piAdvance ) +{ + int iX, iCP, i; + int icChars, icGlyphs; + int piCP; + int clusterSize; + HRESULT hr; + SCRIPT_VISATTR psva[10]; + int piTrailing; + int direction; + + memset(psva,0,sizeof(psva)); + direction = (psa->fRTL)?-1:+1; + + for(iX = -1, i = iCP = 0; i < cChars; i++) + { + if (offsets[i] != iX) + { + iX = offsets[i]; + iCP = i; + } + icChars = cChars; + icGlyphs = cGlyphs; + hr = ScriptXtoCP(iX, icChars, icGlyphs, pwLogClust, psva, piAdvance, psa, &piCP, &piTrailing); + ok_(__FILE__,line)(hr == S_OK, "ScriptXtoCP: should return S_OK not %08x\n", hr); + ok_(__FILE__,line)(piCP == iCP, "ScriptXtoCP: iX=%d should return piCP=%d not %d\n", iX, iCP, piCP); + ok_(__FILE__,line)(piTrailing == 0, "ScriptXtoCP: iX=%d should return piTrailing=0 not %d\n", iX, piTrailing); + } + + for(iX = -2, i = 0; i < cChars; i++) + { + if (offsets[i]+direction != iX) + { + iX = offsets[i] + direction; + iCP = i; + } + icChars = cChars; + icGlyphs = cGlyphs; + hr = ScriptXtoCP(iX, icChars, icGlyphs, pwLogClust, psva, piAdvance, psa, &piCP, &piTrailing); + ok_(__FILE__,line)(hr == S_OK, "ScriptXtoCP leading: should return S_OK not %08x\n", hr); + ok_(__FILE__,line)(piCP == iCP, "ScriptXtoCP leading: iX=%d should return piCP=%d not %d\n", iX, iCP, piCP); + ok_(__FILE__,line)(piTrailing == 0, "ScriptXtoCP leading: iX=%d should return piTrailing=0 not %d\n", iX, piTrailing); + } + + for(clusterSize = 0, iCP = 0, iX = -2, i = 0; i < cChars; i++) + { + clusterSize++; + if (offsets[i] != offsets[i+1]) + { + iX = offsets[i+1]-direction; + icChars = cChars; + icGlyphs = cGlyphs; + hr = ScriptXtoCP(iX, icChars, icGlyphs, pwLogClust, psva, piAdvance, psa, &piCP, &piTrailing); + ok_(__FILE__,line)(hr == S_OK, "ScriptXtoCP trailing: should return S_OK not %08x\n", hr); + ok_(__FILE__,line)(piCP == iCP, "ScriptXtoCP trailing: iX=%d should return piCP=%d not %d\n", iX, iCP, piCP); + ok_(__FILE__,line)(piTrailing == clusterSize, "ScriptXtoCP trailing: iX=%d should return piTrailing=%d not %d\n", iX, clusterSize, piTrailing); + iCP = i+1; + clusterSize = 0; + } + } +} + static void test_ScriptXtoX(void) /**************************************************************************************** * This routine tests the ScriptXtoCP and ScriptCPtoX functions using static variables * @@ -2446,17 +2510,28 @@ static void test_ScriptXtoX(void) WORD pwLogClust[10] = {0, 0, 0, 1, 1, 2, 2, 3, 3, 3}; WORD pwLogClust_RTL[10] = {3, 3, 3, 2, 2, 1, 1, 0, 0, 0}; WORD pwLogClust_2[7] = {4, 3, 3, 2, 1, 0 ,0}; + WORD pwLogClust_3[17] = {0, 1, 1, 1, 1, 4, 5, 6, 6, 8, 8, 8, 8, 11, 11, 13, 13}; + WORD pwLogClust_3_RTL[17] = {13, 13, 11, 11, 8, 8, 8, 8, 6, 6, 5, 4, 1, 1, 1, 1, 0}; int piAdvance[10] = {201, 190, 210, 180, 170, 204, 189, 195, 212, 203}; int piAdvance_2[5] = {39, 26, 19, 17, 11}; + int piAdvance_3[15] = {6, 6, 0, 0, 10, 5, 10, 0, 12, 0, 0, 9, 0, 10, 0}; static const int offsets[13] = {0, 67, 134, 201, 296, 391, 496, 601, 1052, 1503, 1954, 1954, 1954}; static const int offsets_RTL[13] = {781, 721, 661, 601, 496, 391, 296, 201, 134, 67, 0, 0, 0}; static const int offsets_2[10] = {112, 101, 92, 84, 65, 39, 19, 0, 0, 0}; - SCRIPT_VISATTR psva[10]; + + static const int offsets_3[19] = {0, 6, 6, 6, 6, 12, 22, 27, 27, 37, 37, 37, 37, 49, 49, 58, 58, 68, 68}; + static const int offsets_3_RTL[19] = {68, 68, 58, 58, 49, 49, 49, 49, 37, 37, 27, 22, 12, 12, 12, 12, 6, 6}; + + SCRIPT_VISATTR psva[15]; SCRIPT_ANALYSIS sa; - int iX; + SCRIPT_ITEM items[2]; + int iX, i; int piCP; int piTrailing; HRESULT hr; + static const WCHAR hebrW[] = { 0x5be, 0}; + static const WCHAR thaiW[] = { 0xe2a, 0}; + const SCRIPT_PROPERTIES **ppScriptProperties; memset(&sa, 0 , sizeof(SCRIPT_ANALYSIS)); memset(psva, 0, sizeof(psva)); @@ -2518,6 +2593,34 @@ static void test_ScriptXtoX(void) sa.fRTL = TRUE; test_item_ScriptXtoX(&sa, 10, 10, offsets_RTL, pwLogClust_RTL, piAdvance); test_item_ScriptXtoX(&sa, 7, 5, offsets_2, pwLogClust_2, piAdvance_2); + + /* Get thai eScript, This will do LTR and fNeedsCaretInfo */ + hr = ScriptItemize(thaiW, 1, 2, NULL, NULL, items, &i); + ok(hr == S_OK, "got %08x\n", hr); + ok(i == 1, "got %d\n", i); + sa = items[0].a; + + test_caret_item_ScriptXtoCP(&sa, 17, 15, offsets_3, pwLogClust_3, piAdvance_3); + + /* Get hebrew eScript, This will do RTL and fNeedsCaretInfo */ + hr = ScriptItemize(hebrW, 1, 2, NULL, NULL, items, &i); + ok(hr == S_OK, "got %08x\n", hr); + ok(i == 1, "got %d\n", i); + sa = items[0].a; + + /* Note: This behavior CHANGED in uniscribe versions... + * so we only want to test if fNeedsCaretInfo is set */ + hr = ScriptGetProperties(&ppScriptProperties, &i); + if (ppScriptProperties[sa.eScript]->fNeedsCaretInfo) + { + test_caret_item_ScriptXtoCP(&sa, 17, 15, offsets_3_RTL, pwLogClust_3_RTL, piAdvance_3); + hr = ScriptXtoCP(0, 17, 15, pwLogClust_3_RTL, psva, piAdvance_3, &sa, &piCP, &piTrailing); + ok(hr == S_OK, "ScriptXtoCP: should return S_OK not %08x\n", hr); + ok(piCP == 16, "ScriptXtoCP: iX=0 should return piCP=16 not %d\n", piCP); + ok(piTrailing == 1, "ScriptXtoCP: iX=0 should return piTrailing=1 not %d\n", piTrailing); + } + else + win_skip("Uniscribe version too old to test Hebrew clusters\n"); } static void test_ScriptString(HDC hdc) diff --git a/dlls/usp10/usp10.c b/dlls/usp10/usp10.c index f0bbafe515c..8e7bb187259 100644 --- a/dlls/usp10/usp10.c +++ b/dlls/usp10/usp10.c @@ -26,6 +26,7 @@ #include #include +#include #include "windef.h" #include "winbase.h" @@ -2573,9 +2574,100 @@ HRESULT WINAPI ScriptCPtoX(int iCP, return S_OK; } +/* Count the number of characters in a cluster and its starting index*/ +static inline BOOL get_cluster_data(const WORD *pwLogClust, int cChars, int cluster_index, int *cluster_size, int *start_index) +{ + int size = 0; + int i; + + for (i = 0; i < cChars; i++) + { + if (pwLogClust[i] == cluster_index) + { + if (!size && start_index) + { + *start_index = i; + if (!cluster_size) + return TRUE; + } + size++; + } + else if (size) break; + } + if (cluster_size) + *cluster_size = size; + + return (size > 0); +} + +/* + To handle multi-glyph clusters we need to find all the glyphs that are + represented in the cluster. This involves finding the glyph whose + index is the cluster index as well as whose glyph indices are greater than + our cluster index but not part of a new cluster. + + Then we sum all those glyphs' advances. +*/ +static inline int get_cluster_advance(const int* piAdvance, + const SCRIPT_VISATTR *psva, + const WORD *pwLogClust, int cGlyphs, + int cChars, int cluster, int direction) +{ + int glyph_start; + int glyph_end; + int i, advance; + + if (direction > 0) + i = 0; + else + i = (cChars - 1); + + for (glyph_start = -1, glyph_end = -1; i < cChars && i >= 0 && (glyph_start < 0 || glyph_end < 0); i+=direction) + { + if (glyph_start < 0 && pwLogClust[i] != cluster) continue; + if (pwLogClust[i] == cluster && glyph_start < 0) glyph_start = pwLogClust[i]; + if (glyph_start >= 0 && glyph_end < 0 && pwLogClust[i] != cluster) glyph_end = pwLogClust[i]; + } + if (glyph_end < 0) + { + if (direction > 0) + glyph_end = cGlyphs; + else + { + /* Don't fully understand multi-glyph reversed clusters yet, + * do they occur for real or just in our test? */ + FIXME("multi-glyph reversed clusters found\n"); + glyph_end = glyph_start + 1; + } + } + + /* Check for fClusterStart, finding this generally would mean a malformed set of data */ + for (i = glyph_start+1; i< glyph_end; i++) + { + if (psva[i].fClusterStart) + { + glyph_end = i; + break; + } + } + + for (advance = 0, i = glyph_start; i < glyph_end; i++) + advance += piAdvance[i]; + + return advance; +} + + /*********************************************************************** * ScriptXtoCP (USP10.@) * + * Basic algorithm : + * use piAdvance to find the cluster we are looking at + * Find the character that is the first character of the cluster + * That is our base piCP + * If the script snaps to cluster boundries (Hebrew, Indic, Thai) then we + * are good Otherwise if the cluster is larger than 1 glyph we need to + * determine how far through the cluster to advance the cursor. */ HRESULT WINAPI ScriptXtoCP(int iX, int cChars, @@ -2587,16 +2679,11 @@ HRESULT WINAPI ScriptXtoCP(int iX, int *piCP, int *piTrailing) { - int item; - float iPosX; - float iLastPosX; - int iSpecial = -1; - int iCluster = -1; - int clust_size = 1; - int cjump = 0; - int advance; - float special_size = 0.0; int direction = 1; + int iPosX; + int i; + int glyph_index, cluster_index; + int cluster_size; TRACE("(%d,%d,%d,%p,%p,%p,%p,%p,%p)\n", iX, cChars, cGlyphs, pwLogClust, psva, piAdvance, @@ -2605,128 +2692,156 @@ HRESULT WINAPI ScriptXtoCP(int iX, if (psa->fRTL && ! psa->fLogicalOrder) direction = -1; - if (direction<0) + /* Handle an iX < 0 */ + if (iX < 0) { - int max_clust = pwLogClust[0]; - - if (iX < 0) + if (direction < 0) { *piCP = cChars; *piTrailing = 0; - return S_OK; } + else + { + *piCP = -1; + *piTrailing = 1; + } + return S_OK; + } - for (item=0; item < cChars; item++) - if (pwLogClust[item] > max_clust) + /* Looking for non-reversed clusters in a reversed string */ + if (direction < 0) + { + int max_clust = pwLogClust[0]; + for (i=0; i< cChars; i++) + if (pwLogClust[i] > max_clust) { - ERR("We do not handle non reversed clusters properly\n"); + FIXME("We do not handle non reversed clusters properly\n"); break; } } - if (iX < 0) + /* find the glyph_index based in iX */ + if (direction > 0) { - *piCP = -1; - *piTrailing = 1; - return S_OK; + for (glyph_index = -1, iPosX = iX; iPosX >=0 && glyph_index < cGlyphs; iPosX -= piAdvance[glyph_index+1], glyph_index++) + ; + } + else + { + for (glyph_index = -1, iPosX = iX; iPosX > 0 && glyph_index < cGlyphs; iPosX -= piAdvance[glyph_index+1], glyph_index++) + ; } - iPosX = iLastPosX = 0; - if (direction > 0) - item = 0; - else - item = cChars - 1; - for (; iPosX <= iX && item < cChars && item >= 0; item+=direction) + TRACE("iPosX %i -> glyph_index %i (%i)\n", iPosX, glyph_index, cGlyphs); + + *piTrailing = 0; + if (glyph_index >= 0 && glyph_index < cGlyphs) { - iLastPosX = iPosX; - if (iSpecial == -1 && - (iCluster == -1 || - (iCluster != -1 && - ((direction > 0 && iCluster+clust_size <= item) || - (direction < 0 && iCluster-clust_size >= item)) - ) - ) - ) + /* find the cluster */ + if (direction > 0 ) + for (i = 0, cluster_index = pwLogClust[0]; i < cChars && pwLogClust[i] <= glyph_index; cluster_index=pwLogClust[i++]) + ; + else + for (i = 0, cluster_index = pwLogClust[0]; i < cChars && pwLogClust[i] >= glyph_index; cluster_index=pwLogClust[i++]) + ; + + TRACE("cluster_index %i\n", cluster_index); + + if (direction < 0 && iPosX >= 0 && glyph_index != cluster_index) { - int check; - int clust = pwLogClust[item]; + /* We are off the end of the string */ + *piCP = -1; + *piTrailing = 1; + return S_OK; + } - iCluster = -1; - cjump = 0; - clust_size = get_cluster_size(pwLogClust, cChars, item, direction, - &iCluster, &check); - advance = get_glyph_cluster_advance(piAdvance, psva, pwLogClust, cGlyphs, cChars, clust, direction); + get_cluster_data(pwLogClust, cChars, cluster_index, &cluster_size, &i); - if (check >= cChars && direction > 0) + TRACE("first char index %i\n",i); + if (scriptInformation[psa->eScript].props.fNeedsCaretInfo) + { + /* Check trailing */ + if (glyph_index != cluster_index || + (direction > 0 && abs(iPosX) <= (piAdvance[glyph_index] / 2)) || + (direction < 0 && abs(iPosX) >= (piAdvance[glyph_index] / 2))) + *piTrailing = cluster_size; + } + else + { + if (cluster_size > 1) { - int glyph; - for (glyph = clust; glyph < cGlyphs; glyph++) - special_size += get_glyph_cluster_advance(piAdvance, psva, pwLogClust, cGlyphs, cChars, glyph, direction); - iSpecial = item; - special_size /= (cChars - item); - iPosX += special_size; - } - else - { - if (scriptInformation[psa->eScript].props.fNeedsCaretInfo) + /* Be part way through the glyph cluster based on size and position */ + int cluster_advance = get_cluster_advance(piAdvance, psva, pwLogClust, cGlyphs, cChars, cluster_index, direction); + double cluster_part_width = cluster_advance / (float)cluster_size; + double adv; + int part_index; + + /* back up to the beginning of the cluster */ + for (adv = iPosX, part_index = cluster_index; part_index <= glyph_index; part_index++) + adv += piAdvance[part_index]; + if (adv > iX) adv = iX; + + TRACE("Multi-char cluster, no snap\n"); + TRACE("cluster size %i, pre-cluster iPosX %f\n",cluster_size, adv); + TRACE("advance %i divides into %f per char\n", cluster_advance, cluster_part_width); + if (direction > 0) { - if (!cjump) - iPosX += advance; - cjump++; + for (part_index = 0; adv >= 0; adv-=cluster_part_width, part_index++) + ; + if (part_index) part_index--; } else - iPosX += advance / (float)clust_size; - } - } - else if (iSpecial != -1) - iPosX += special_size; - else /* (iCluster != -1) */ - { - int adv = get_glyph_cluster_advance(piAdvance, psva, pwLogClust, cGlyphs, cChars, pwLogClust[iCluster], direction); - if (scriptInformation[psa->eScript].props.fNeedsCaretInfo) - { - if (!cjump) - iPosX += adv; - cjump++; + { + for (part_index = 0; adv > 0; adv-=cluster_part_width, part_index++) + ; + if (part_index > cluster_size) + { + adv += cluster_part_width; + part_index=cluster_size; + } + } + + TRACE("base_char %i part_index %i, leftover advance %f\n",i, part_index, adv); + + if (direction > 0) + i += part_index; + else + i += (cluster_size - part_index); + + /* Check trailing */ + if ((direction > 0 && fabs(adv) <= (cluster_part_width / 2.0)) || + (direction < 0 && adv && fabs(adv) >= (cluster_part_width / 2.0))) + *piTrailing = 1; } else - iPosX += adv / (float)clust_size; + { + /* Check trailing */ + if ((direction > 0 && abs(iPosX) <= (piAdvance[glyph_index] / 2)) || + (direction < 0 && abs(iPosX) >= (piAdvance[glyph_index] / 2))) + *piTrailing = 1; + } } } - - if (direction > 0) - { - if (iPosX > iX) - item--; - if (item < cChars && ((iPosX - iLastPosX) / 2.0) + iX >= iPosX) - { - if (scriptInformation[psa->eScript].props.fNeedsCaretInfo && clust_size > 1) - item+=(clust_size-1); - *piTrailing = 1; - } - else - *piTrailing = 0; - } else { - if (iX == iLastPosX) - item++; - if (iX >= iLastPosX && iX <= iPosX) - item++; + TRACE("Point falls outside of string\n"); + if (glyph_index < 0) + i = cChars-1; + else /* (glyph_index >= cGlyphs) */ + i = cChars; - if (iLastPosX == iX) - *piTrailing = 0; - else if (item < 0 || ((iLastPosX - iPosX) / 2.0) + iX <= iLastPosX) + /* If not snaping in the reverse direction (such as Hebrew) Then 0 + point flow to the next character */ + if (direction < 0) { - if (scriptInformation[psa->eScript].props.fNeedsCaretInfo && clust_size > 1) - item-=(clust_size-1); - *piTrailing = 1; + if (!scriptInformation[psa->eScript].props.fNeedsCaretInfo && abs(iPosX) == piAdvance[glyph_index]) + i++; + else + *piTrailing = 1; } - else - *piTrailing = 0; } - *piCP = item; + *piCP = i; TRACE("*piCP=%d\n", *piCP); TRACE("*piTrailing=%d\n", *piTrailing);