Sweden-Number/dlls/gdi32/bidi.c

614 lines
20 KiB
C
Raw Normal View History

/*
* 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/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 */
/* grep -r ';BN;' data.txt | grep -v [0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F] | sed -e s@\;.*@@ -e s/^..../0x\&,\ / | xargs echo */
static const WCHAR BNs[] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
0x000E, 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016,
0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x007F, 0x0080, 0x0081, 0x0082,
0x0083, 0x0084, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C,
0x008D, 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095,
0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E,
0x009F, 0x00AD, 0x070F, 0x200B, 0x200C, 0x200D, 0x2060, 0x2061, 0x2062,
0x2063, 0x206A, 0x206B, 0x206C, 0x206D, 0x206E, 0x206F, 0xFEFF
};
/* Idem, but with ';R;' instead of ';BN;' */
static const WCHAR Rs[] = {
0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4,
0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD,
0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6,
0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4,
0x07C0, 0x07C1, 0x07C2, 0x07C3, 0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8,
0x07C9, 0x07CA, 0x07CB, 0x07CC, 0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1,
0x07D2, 0x07D3, 0x07D4, 0x07D5, 0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA,
0x07DB, 0x07DC, 0x07DD, 0x07DE, 0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3,
0x07E4, 0x07E5, 0x07E6, 0x07E7, 0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5,
0x07FA, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22, 0xFB23, 0xFB24,
0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C, 0xFB2D, 0xFB2E,
0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35, 0xFB36, 0xFB38,
0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44,
0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C, 0xFB4D, 0xFB4E,
0xFB4F
};
/* Convert the incomplete win32 table to some slightly more useful data */
static void classify(LPCWSTR lpString, WORD *chartype, DWORD uCount)
{
unsigned i, j;
GetStringTypeW(CT_CTYPE2, lpString, uCount, chartype);
for (i = 0; i < uCount; ++i)
switch (chartype[i])
{
case C2_LEFTTORIGHT: chartype[i] = L; break;
case C2_RIGHTTOLEFT:
chartype[i] = AL;
for (j = 0; j < sizeof(Rs)/sizeof(WCHAR); ++j)
if (Rs[j] == lpString[i])
{
chartype[i] = R;
break;
}
break;
case C2_EUROPENUMBER: chartype[i] = EN; break;
case C2_EUROPESEPARATOR: chartype[i] = ES; break;
case C2_EUROPETERMINATOR: chartype[i] = ET; break;
case C2_ARABICNUMBER: chartype[i] = AN; break;
case C2_COMMONSEPARATOR: chartype[i] = CS; break;
case C2_BLOCKSEPARATOR: chartype[i] = B; break;
case C2_SEGMENTSEPARATOR: chartype[i] = S; break;
case C2_WHITESPACE: chartype[i] = WS; break;
case C2_OTHERNEUTRAL:
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;
default: chartype[i] = ON; break;
}
break;
case C2_NOTAPPLICABLE:
chartype[i] = NSM;
for (j = 0; j < sizeof(BNs)/sizeof(WCHAR); ++j)
if (BNs[j] == lpString[i])
{
chartype[i] = BN;
break;
}
break;
default:
/* According to BiDi spec, unassigned characters default to L */
FIXME("Unhandled character type: %04x\n", chartype[i]);
chartype[i] = L;
break;
}
}
2007-09-15 15:15:30 +02:00
/* 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)
2007-09-15 15:15:30 +02:00
{
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
2007-12-07 21:01:34 +01:00
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: Character count
In/Out: Array of characters
Array of line break flags
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, 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);
}
/* DISPLAY OPTIONS */
/*-----------------------------------------------------------------------
Function: mirror
Crudely implements rule L4 of the Unicode Bidirectional Algorithm
Demonstrate mirrored brackets, braces and parens
Input: Array of levels
Count of characters
In/Out: Array of characters (should be array of glyph ids)
Note;
A full implementation would need to substitute mirrored glyphs even
for characters that are not paired (e.g. integral sign).
-----------------------------------------------------------------------*/
static void mirror(LPWSTR pszInput, const BYTE* plevel, int cch)
{
static int warn_once;
int i;
for (i = 0; i < cch; ++i)
{
if (!odd(plevel[i]))
continue;
/* This needs the data from http://www.unicode.org/Public/UNIDATA/BidiMirroring.txt */
if (!warn_once++)
FIXME("stub: mirroring of characters not yet implemented\n");
break;
}
}
/*------------------------------------------------------------------------
Function: BidiLines
Implements the Line-by-Line phases of the Unicode Bidi Algorithm
Input: Count of characters
flag whether to mirror
Inp/Out: Input text
Array of character directions
Array of levels
------------------------------------------------------------------------*/
static void BidiLines(int baselevel, LPWSTR pszOutLine, LPCWSTR pszLine, WORD * pclsLine,
BYTE * plevelLine, int cchPara, int fMirror, 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;
if (fMirror)
mirror(pszOutLine, plevelLine, cchLine);
/* reorder each line in place */
ScriptLayout(cchLine, plevelLine, run, NULL);
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
*/
BOOL BIDI_Reorder(
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 *chartype;
BYTE *levels;
unsigned i, done;
int maxItems;
int nItems;
SCRIPT_CONTROL Control;
SCRIPT_STATE State;
SCRIPT_ITEM *pItems;
HRESULT res;
TRACE("%s, %d, 0x%08x lpOutString=%p, lpOrder=%p\n",
2004-01-27 01:01:43 +01:00
debugstr_wn(lpString, uCount), uCount, dwFlags,
lpOutString, lpOrder);
memset(&Control, 0, sizeof(Control));
memset(&State, 0, sizeof(State));
if (!(dwFlags & GCP_REORDER))
{
FIXME("Asked to reorder without reorder flag set\n");
return FALSE;
}
if (uCountOut < uCount)
{
2007-09-20 21:35:07 +02:00
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));
/* 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
{
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);
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;
}
2008-11-13 00:34:01 +01:00
done = 0;
while (done < uCount)
{
unsigned j;
classify(lpString + done, chartype, uCount - done);
/* limit text to first block */
i = resolveParagraphs(chartype, uCount - done);
2007-09-28 16:22:15 +02:00
for (j = 0; j < i; ++j)
switch(chartype[j])
{
case B:
case S:
case WS:
2007-09-28 16:22:15 +02:00
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;
}
2007-09-28 16:22:15 +02:00
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);
return FALSE;
}
res = ScriptItemize(lpString + done, i, maxItems, &Control, &State, pItems, &nItems);
}
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;
}
/* assign directional types again, but for WS, S this time */
classify(lpString + done, chartype, i);
BidiLines(State.uBidiLevel, lpOutString ? lpOutString + done : NULL, lpString + done,
chartype, levels, i, !(dwFlags & GCP_SYMSWAPOFF), 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;
}
done += i;
}
HeapFree(GetProcessHeap(), 0, chartype);
HeapFree(GetProcessHeap(), 0, levels);
HeapFree(GetProcessHeap(), 0, pItems);
return TRUE;
}