/* * GDI BiDirectional handling * * Copyright 2003 Shachar Shemesh * Copyright 2007 Maarten Lankhorst * Copyright 2010 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 * * Code derived from the modified reference implementation * that was found in revision 17 of http://unicode.org/reports/tr9/ * "Unicode Standard Annex #9: THE BIDIRECTIONAL ALGORITHM" * * -- Copyright (C) 1999-2005, ASMUS, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of the Unicode data files and any associated documentation (the * "Data Files") or Unicode software and any associated documentation (the * "Software") to deal in the Data Files or Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, and/or sell copies of the Data Files or Software, * and to permit persons to whom the Data Files or Software are furnished * to do so, provided that (a) the above copyright notice(s) and this * permission notice appear with all copies of the Data Files or Software, * (b) both the above copyright notice(s) and this permission notice appear * in associated documentation, and (c) there is clear notice in each * modified Data File or in the Software as well as in the documentation * associated with the Data File(s) or Software that the data or software * has been modified. */ #include "config.h" #include <stdarg.h> #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winnls.h" #include "usp10.h" #include "wine/unicode.h" #include "wine/debug.h" #include "gdi_private.h" WINE_DEFAULT_DEBUG_CHANNEL(bidi); /* HELPER FUNCTIONS AND DECLARATIONS */ #define odd(x) ((x) & 1) /*------------------------------------------------------------------------ Bidirectional Character Types as defined by the Unicode Bidirectional Algorithm Table 3-7. Note: The list of bidirectional character types here is not grouped the same way as the table 3-7, since the numberic values for the types are chosen to keep the state and action tables compact. ------------------------------------------------------------------------*/ enum directions { /* input types */ /* ON MUST be zero, code relies on ON = N = 0 */ ON = 0, /* Other Neutral */ L, /* Left Letter */ R, /* Right Letter */ AN, /* Arabic Number */ EN, /* European Number */ AL, /* Arabic Letter (Right-to-left) */ NSM, /* Non-spacing Mark */ CS, /* Common Separator */ ES, /* European Separator */ ET, /* European Terminator (post/prefix e.g. $ and %) */ /* resolved types */ BN, /* Boundary neutral (type of RLE etc after explicit levels) */ /* input types, */ S, /* Segment Separator (TAB) // used only in L1 */ WS, /* White space // used only in L1 */ B, /* Paragraph Separator (aka as PS) */ /* types for explicit controls */ RLO, /* these are used only in X1-X9 */ RLE, LRO, LRE, PDF, /* resolved types, also resolved directions */ N = ON, /* alias, where ON, WS and S are treated the same */ }; /* HELPER FUNCTIONS */ /* Convert the libwine information to the direction enum */ static void classify(LPCWSTR lpString, WORD *chartype, DWORD uCount) { static const enum directions dir_map[16] = { L, /* unassigned defaults to L */ L, R, EN, ES, ET, AN, CS, B, S, WS, ON, AL, NSM, BN, PDF /* also LRE, LRO, RLE, RLO */ }; unsigned i; for (i = 0; i < uCount; ++i) { chartype[i] = dir_map[get_char_typeW(lpString[i]) >> 12]; if (chartype[i] == PDF) { switch (lpString[i]) { case 0x202A: chartype[i] = LRE; break; case 0x202B: chartype[i] = RLE; break; case 0x202C: chartype[i] = PDF; break; case 0x202D: chartype[i] = LRO; break; case 0x202E: chartype[i] = RLO; break; } } } } /* Set a run of cval values at locations all prior to, but not including */ /* iStart, to the new value nval. */ static void SetDeferredRun(BYTE *pval, int cval, int iStart, int nval) { int i = iStart - 1; for (; i >= iStart - cval; i--) { pval[i] = nval; } } /* THE PARAGRAPH LEVEL */ /*------------------------------------------------------------------------ Function: resolveParagraphs Resolves the input strings into blocks over which the algorithm is then applied. Implements Rule P1 of the Unicode Bidi Algorithm Input: Text string Character count Output: revised character count Note: This is a very simplistic function. In effect it restricts the action of the algorithm to the first paragraph in the input where a paragraph ends at the end of the first block separator or at the end of the input text. ------------------------------------------------------------------------*/ static int resolveParagraphs(WORD *types, int cch) { /* skip characters not of type B */ int ich = 0; for(; ich < cch && types[ich] != B; ich++); /* stop after first B, make it a BN for use in the next steps */ if (ich < cch && types[ich] == B) types[ich++] = BN; return ich; } /* REORDER */ /*------------------------------------------------------------------------ Function: resolveLines Breaks a paragraph into lines Input: Array of line break flags Character count In/Out: Array of characters Returns the count of characters on the first line Note: This function only breaks lines at hard line breaks. Other line breaks can be passed in. If pbrk[n] is TRUE, then a break occurs after the character in pszInput[n]. Breaks before the first character are not allowed. ------------------------------------------------------------------------*/ static int resolveLines(LPCWSTR pszInput, const BOOL * pbrk, int cch) { /* skip characters not of type LS */ int ich = 0; for(; ich < cch; ich++) { if (pszInput[ich] == (WCHAR)'\n' || (pbrk && pbrk[ich])) { ich++; break; } } return ich; } /*------------------------------------------------------------------------ Function: resolveWhiteSpace Resolves levels for WS and S Implements rule L1 of the Unicode bidi Algorithm. Input: Base embedding level Character count Array of direction classes (for one line of text) In/Out: Array of embedding levels (for one line of text) Note: this should be applied a line at a time. The default driver code supplied in this file assumes a single line of text; for a real implementation, cch and the initial pointer values would have to be adjusted. ------------------------------------------------------------------------*/ static void resolveWhitespace(int baselevel, const WORD *pcls, BYTE *plevel, int cch) { int cchrun = 0; BYTE oldlevel = baselevel; int ich = 0; for (; ich < cch; ich++) { switch(pcls[ich]) { default: cchrun = 0; /* any other character breaks the run */ break; case WS: cchrun++; break; case RLE: case LRE: case LRO: case RLO: case PDF: case BN: plevel[ich] = oldlevel; cchrun++; break; case S: case B: /* reset levels for WS before eot */ SetDeferredRun(plevel, cchrun, ich, baselevel); cchrun = 0; plevel[ich] = baselevel; break; } oldlevel = plevel[ich]; } /* reset level before eot */ SetDeferredRun(plevel, cchrun, ich, baselevel); } /*------------------------------------------------------------------------ Function: BidiLines Implements the Line-by-Line phases of the Unicode Bidi Algorithm Input: Count of characters Array of character directions Inp/Out: Input text Array of levels ------------------------------------------------------------------------*/ static void BidiLines(int baselevel, LPWSTR pszOutLine, LPCWSTR pszLine, const WORD * pclsLine, BYTE * plevelLine, int cchPara, const BOOL * pbrk) { int cchLine = 0; int done = 0; int *run; run = HeapAlloc(GetProcessHeap(), 0, cchPara * sizeof(int)); if (!run) { WARN("Out of memory\n"); return; } do { /* break lines at LS */ cchLine = resolveLines(pszLine, pbrk, cchPara); /* resolve whitespace */ resolveWhitespace(baselevel, pclsLine, plevelLine, cchLine); if (pszOutLine) { int i; /* reorder each line in place */ ScriptLayout(cchLine, plevelLine, NULL, run); for (i = 0; i < cchLine; i++) pszOutLine[done+run[i]] = pszLine[i]; } pszLine += cchLine; plevelLine += cchLine; pbrk += pbrk ? cchLine : 0; pclsLine += cchLine; cchPara -= cchLine; done += cchLine; } while (cchPara); HeapFree(GetProcessHeap(), 0, run); } /************************************************************* * BIDI_Reorder * * Returns TRUE if reordering was required and done. */ BOOL BIDI_Reorder( HDC hDC, /*[in] Display DC */ LPCWSTR lpString, /* [in] The string for which information is to be returned */ INT uCount, /* [in] Number of WCHARs in string. */ DWORD dwFlags, /* [in] GetCharacterPlacement compatible flags specifying how to process the string */ DWORD dwWineGCP_Flags, /* [in] Wine internal flags - Force paragraph direction */ LPWSTR lpOutString, /* [out] Reordered string */ INT uCountOut, /* [in] Size of output buffer */ UINT *lpOrder, /* [out] Logical -> Visual order map */ WORD **lpGlyphs, /* [out] reordered, mirrored, shaped glyphs to display */ INT *cGlyphs /* [out] number of glyphs generated */ ) { WORD *chartype; BYTE *levels; INT i, done; unsigned glyph_i; BOOL is_complex; int maxItems; int nItems; SCRIPT_CONTROL Control; SCRIPT_STATE State; SCRIPT_ITEM *pItems; HRESULT res; SCRIPT_CACHE psc = NULL; WORD *run_glyphs = NULL; WORD *pwLogClust = NULL; SCRIPT_VISATTR *psva = NULL; DWORD cMaxGlyphs = 0; BOOL doGlyphs = TRUE; TRACE("%s, %d, 0x%08x lpOutString=%p, lpOrder=%p\n", debugstr_wn(lpString, uCount), uCount, dwFlags, lpOutString, lpOrder); memset(&Control, 0, sizeof(Control)); memset(&State, 0, sizeof(State)); if (lpGlyphs) *lpGlyphs = NULL; if (!(dwFlags & GCP_REORDER)) { FIXME("Asked to reorder without reorder flag set\n"); return FALSE; } if (lpOutString && uCountOut < uCount) { FIXME("lpOutString too small\n"); return FALSE; } chartype = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(WORD)); if (!chartype) { WARN("Out of memory\n"); return FALSE; } if (lpOutString) memcpy(lpOutString, lpString, uCount * sizeof(WCHAR)); is_complex = FALSE; for (i = 0; i < uCount && !is_complex; i++) { if ((lpString[i] >= 0x900 && lpString[i] <= 0xfff) || (lpString[i] >= 0x1cd0 && lpString[i] <= 0x1cff) || (lpString[i] >= 0xa840 && lpString[i] <= 0xa8ff)) is_complex = TRUE; } /* Verify reordering will be required */ if ((WINE_GCPW_FORCE_RTL == (dwWineGCP_Flags&WINE_GCPW_DIR_MASK)) || ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_RTL)) State.uBidiLevel = 1; else if (!is_complex) { done = 1; classify(lpString, chartype, uCount); for (i = 0; i < uCount; i++) switch (chartype[i]) { case R: case AL: case RLE: case RLO: done = 0; break; } if (done) { HeapFree(GetProcessHeap(), 0, chartype); if (lpOrder) { for (i = 0; i < uCount; i++) lpOrder[i] = i; } return TRUE; } } levels = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(BYTE)); if (!levels) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); return FALSE; } maxItems = 5; pItems = HeapAlloc(GetProcessHeap(),0, maxItems * sizeof(SCRIPT_ITEM)); if (!pItems) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); return FALSE; } if (lpGlyphs) { cMaxGlyphs = 1.5 * uCount + 16; run_glyphs = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * cMaxGlyphs); if (!run_glyphs) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); return FALSE; } pwLogClust = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * uCount); if (!pwLogClust) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); HeapFree(GetProcessHeap(), 0, run_glyphs); return FALSE; } psva = HeapAlloc(GetProcessHeap(),0,sizeof(SCRIPT_VISATTR) * uCount); if (!psva) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); HeapFree(GetProcessHeap(), 0, run_glyphs); HeapFree(GetProcessHeap(), 0, pwLogClust); return FALSE; } } done = 0; glyph_i = 0; while (done < uCount) { INT j; classify(lpString + done, chartype, uCount - done); /* limit text to first block */ i = resolveParagraphs(chartype, uCount - done); for (j = 0; j < i; ++j) switch(chartype[j]) { case B: case S: case WS: case ON: chartype[j] = N; default: continue; } if ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_RTL) State.uBidiLevel = 1; else if ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_LTR) State.uBidiLevel = 0; if (dwWineGCP_Flags & WINE_GCPW_LOOSE_MASK) { for (j = 0; j < i; ++j) if (chartype[j] == L) { State.uBidiLevel = 0; break; } else if (chartype[j] == R || chartype[j] == AL) { State.uBidiLevel = 1; break; } } res = ScriptItemize(lpString + done, i, maxItems, &Control, &State, pItems, &nItems); while (res == E_OUTOFMEMORY) { maxItems = maxItems * 2; pItems = HeapReAlloc(GetProcessHeap(), 0, pItems, sizeof(SCRIPT_ITEM) * maxItems); if (!pItems) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, run_glyphs); HeapFree(GetProcessHeap(), 0, pwLogClust); HeapFree(GetProcessHeap(), 0, psva); return FALSE; } res = ScriptItemize(lpString + done, i, maxItems, &Control, &State, pItems, &nItems); } if (lpOutString || lpOrder) for (j = 0; j < nItems; j++) { int k; for (k = pItems[j].iCharPos; k < pItems[j+1].iCharPos; k++) levels[k] = pItems[j].a.s.uBidiLevel; } if (lpOutString) { /* assign directional types again, but for WS, S this time */ classify(lpString + done, chartype, i); BidiLines(State.uBidiLevel, lpOutString + done, lpString + done, chartype, levels, i, 0); } if (lpOrder) { int k, lastgood; for (j = lastgood = 0; j < i; ++j) if (levels[j] != levels[lastgood]) { --j; if (odd(levels[lastgood])) for (k = j; k >= lastgood; --k) lpOrder[done + k] = done + j - k; else for (k = lastgood; k <= j; ++k) lpOrder[done + k] = done + k; lastgood = ++j; } if (odd(levels[lastgood])) for (k = j - 1; k >= lastgood; --k) lpOrder[done + k] = done + j - 1 - k; else for (k = lastgood; k < j; ++k) lpOrder[done + k] = done + k; } if (lpGlyphs && doGlyphs) { BYTE *runOrder; int *visOrder; SCRIPT_ITEM *curItem; runOrder = HeapAlloc(GetProcessHeap(), 0, maxItems * sizeof(*runOrder)); visOrder = HeapAlloc(GetProcessHeap(), 0, maxItems * sizeof(*visOrder)); if (!runOrder || !visOrder) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, runOrder); HeapFree(GetProcessHeap(), 0, visOrder); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); HeapFree(GetProcessHeap(), 0, psva); HeapFree(GetProcessHeap(), 0, pwLogClust); return FALSE; } for (j = 0; j < nItems; j++) runOrder[j] = pItems[j].a.s.uBidiLevel; ScriptLayout(nItems, runOrder, visOrder, NULL); for (j = 0; j < nItems; j++) { int k; int cChars,cOutGlyphs; curItem = &pItems[visOrder[j]]; cChars = pItems[visOrder[j]+1].iCharPos - curItem->iCharPos; res = ScriptShape(hDC, &psc, lpString + done + curItem->iCharPos, cChars, cMaxGlyphs, &curItem->a, run_glyphs, pwLogClust, psva, &cOutGlyphs); while (res == E_OUTOFMEMORY) { cMaxGlyphs *= 2; run_glyphs = HeapReAlloc(GetProcessHeap(), 0, run_glyphs, sizeof(WORD) * cMaxGlyphs); if (!run_glyphs) { WARN("Out of memory\n"); HeapFree(GetProcessHeap(), 0, runOrder); HeapFree(GetProcessHeap(), 0, visOrder); HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); HeapFree(GetProcessHeap(), 0, psva); HeapFree(GetProcessHeap(), 0, pwLogClust); HeapFree(GetProcessHeap(), 0, *lpGlyphs); ScriptFreeCache(&psc); *lpGlyphs = NULL; return FALSE; } res = ScriptShape(hDC, &psc, lpString + done + curItem->iCharPos, cChars, cMaxGlyphs, &curItem->a, run_glyphs, pwLogClust, psva, &cOutGlyphs); } if (res) { if (res == USP_E_SCRIPT_NOT_IN_FONT) TRACE("Unable to shape with currently selected font\n"); else FIXME("Unable to shape string (%x)\n",res); j = nItems; doGlyphs = FALSE; HeapFree(GetProcessHeap(), 0, *lpGlyphs); *lpGlyphs = NULL; } else { if (*lpGlyphs) *lpGlyphs = HeapReAlloc(GetProcessHeap(), 0, *lpGlyphs, sizeof(WORD) * (glyph_i + cOutGlyphs)); else *lpGlyphs = HeapAlloc(GetProcessHeap(), 0, sizeof(WORD) * (glyph_i + cOutGlyphs)); for (k = 0; k < cOutGlyphs; k++) (*lpGlyphs)[glyph_i+k] = run_glyphs[k]; glyph_i += cOutGlyphs; } } HeapFree(GetProcessHeap(), 0, runOrder); HeapFree(GetProcessHeap(), 0, visOrder); } done += i; } if (cGlyphs) *cGlyphs = glyph_i; HeapFree(GetProcessHeap(), 0, chartype); HeapFree(GetProcessHeap(), 0, levels); HeapFree(GetProcessHeap(), 0, pItems); HeapFree(GetProcessHeap(), 0, run_glyphs); HeapFree(GetProcessHeap(), 0, pwLogClust); HeapFree(GetProcessHeap(), 0, psva); ScriptFreeCache(&psc); return TRUE; }