653 lines
20 KiB
C
653 lines
20 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 "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: 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);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------
|
|
Function: BidiLines
|
|
|
|
Implements the Line-by-Line phases of the Unicode Bidi Algorithm
|
|
|
|
Input: Count of characters
|
|
|
|
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, 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, 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
|
|
*
|
|
* 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;
|
|
unsigned i, done, glyph_i;
|
|
|
|
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));
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
unsigned 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);
|
|
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)
|
|
{
|
|
int j;
|
|
BYTE runOrder[maxItems];
|
|
int visOrder[maxItems];
|
|
SCRIPT_ITEM *curItem;
|
|
|
|
for (j = 0; j < nItems; j++)
|
|
runOrder[j] = pItems[j].a.s.uBidiLevel;
|
|
|
|
ScriptLayout(nItems, runOrder, NULL, visOrder);
|
|
|
|
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, 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 && res != USP_E_SCRIPT_NOT_IN_FONT)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|