reactos/dll/win32/lpk/bidi.c
Baruch Rutman a4a59ad413 [GDI32][LPK] BiDi support for ExtTextOut and GetCharacterPlacement (#534)
Introduce BiDi (bi-directional text) support for ExtTextOut and GetCharacterPlacement, using Wine's GDI BIDI_Reorder function.
Solves the main issue with CORE-7003.

To be compatible with Win2k3+, introduce the "Language Pack" (LPK) dll.
- All the bidi code is removed from gdi32 and replaced by calls to LPK.
  Gdi32 uses dynamic linking to lpk.dll. In case of linking failure no bidi processing will be available.
- Implemented LpkGetCharacterPlacement.
- Implement LpkExtTextOut.
- Add a demo test program to show how the apis should function.
- Added all the remaining code, added special case for lpDx calculation if also GCP_GLYPHSHAPE flag was called.
  Applications that call GCP that use GCP_GLYPHSHAPE flags should also use the GCP_REORDER flag.
  (As written in https://msdn.microsoft.com/en-us/library/windows/desktop/dd144860(v=vs.85).aspx )
- Add ETO_RTLREADING flag handling.
  Imported the ETO_RTLREADING flag handling from wine, which changes the string part order (runs).
  A RRR1LLLRRR2 string without will show as RRR1LLLRRR2 without it, with it RRR2LLLRRR1.
2018-05-30 14:41:22 +02:00

688 lines
22 KiB
C

/*
* 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 "ros_lpk.h"
#include "wine/unicode.h"
#include "wine/debug.h"
//#include "config.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;
}