1 /*
2 * GDI BiDirectional handling
3 *
4 * Copyright 2003 Shachar Shemesh
5 * Copyright 2007 Maarten Lankhorst
6 * Copyright 2010 CodeWeavers, Aric Stewart
7 *
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 * Code derived from the modified reference implementation
24 * that was found in revision 17 of http://unicode.org/reports/tr9/
25 * "Unicode Standard Annex #9: THE BIDIRECTIONAL ALGORITHM"
26 *
27 * -- Copyright (C) 1999-2005, ASMUS, Inc.
28 *
29 * Permission is hereby granted, free of charge, to any person obtaining a
30 * copy of the Unicode data files and any associated documentation (the
31 * "Data Files") or Unicode software and any associated documentation (the
32 * "Software") to deal in the Data Files or Software without restriction,
33 * including without limitation the rights to use, copy, modify, merge,
34 * publish, distribute, and/or sell copies of the Data Files or Software,
35 * and to permit persons to whom the Data Files or Software are furnished
36 * to do so, provided that (a) the above copyright notice(s) and this
37 * permission notice appear with all copies of the Data Files or Software,
38 * (b) both the above copyright notice(s) and this permission notice appear
39 * in associated documentation, and (c) there is clear notice in each
40 * modified Data File or in the Software as well as in the documentation
41 * associated with the Data File(s) or Software that the data or software
42 * has been modified.
43 */
44
45 #include "ros_lpk.h"
46
47 WINE_DEFAULT_DEBUG_CHANNEL(bidi);
48
49 /* HELPER FUNCTIONS AND DECLARATIONS */
50
51 #define odd(x) ((x) & 1)
52
53 extern const unsigned short bidi_direction_table[] DECLSPEC_HIDDEN;
54
55 /*------------------------------------------------------------------------
56 Bidirectional Character Types
57
58 as defined by the Unicode Bidirectional Algorithm Table 3-7.
59
60 Note:
61
62 The list of bidirectional character types here is not grouped the
63 same way as the table 3-7, since the numberic values for the types
64 are chosen to keep the state and action tables compact.
65 ------------------------------------------------------------------------*/
66 enum directions
67 {
68 /* input types */
69 /* ON MUST be zero, code relies on ON = N = 0 */
70 ON = 0, /* Other Neutral */
71 L, /* Left Letter */
72 R, /* Right Letter */
73 AN, /* Arabic Number */
74 EN, /* European Number */
75 AL, /* Arabic Letter (Right-to-left) */
76 NSM, /* Non-spacing Mark */
77 CS, /* Common Separator */
78 ES, /* European Separator */
79 ET, /* European Terminator (post/prefix e.g. $ and %) */
80
81 /* resolved types */
82 BN, /* Boundary neutral (type of RLE etc after explicit levels) */
83
84 /* input types, */
85 S, /* Segment Separator (TAB) // used only in L1 */
86 WS, /* White space // used only in L1 */
87 B, /* Paragraph Separator (aka as PS) */
88
89 /* types for explicit controls */
90 RLO, /* these are used only in X1-X9 */
91 RLE,
92 LRO,
93 LRE,
94 PDF,
95
96 LRI, /* Isolate formatting characters new with 6.3 */
97 RLI,
98 FSI,
99 PDI,
100
101 /* resolved types, also resolved directions */
102 NI = ON, /* alias, where ON, WS and S are treated the same */
103 };
104
105 /* HELPER FUNCTIONS */
106
get_table_entry(const unsigned short * table,WCHAR ch)107 static inline unsigned short get_table_entry(const unsigned short *table, WCHAR ch)
108 {
109 return table[table[table[ch >> 8] + ((ch >> 4) & 0x0f)] + (ch & 0xf)];
110 }
111
112 /* Convert the libwine information to the direction enum */
classify(LPCWSTR lpString,WORD * chartype,DWORD uCount)113 static void classify(LPCWSTR lpString, WORD *chartype, DWORD uCount)
114 {
115 unsigned i;
116
117 for (i = 0; i < uCount; ++i)
118 chartype[i] = get_table_entry( bidi_direction_table, lpString[i] );
119 }
120
121 /* Set a run of cval values at locations all prior to, but not including */
122 /* iStart, to the new value nval. */
SetDeferredRun(BYTE * pval,int cval,int iStart,int nval)123 static void SetDeferredRun(BYTE *pval, int cval, int iStart, int nval)
124 {
125 int i = iStart - 1;
126 for (; i >= iStart - cval; i--)
127 {
128 pval[i] = nval;
129 }
130 }
131
132 /* THE PARAGRAPH LEVEL */
133
134 /*------------------------------------------------------------------------
135 Function: resolveParagraphs
136
137 Resolves the input strings into blocks over which the algorithm
138 is then applied.
139
140 Implements Rule P1 of the Unicode Bidi Algorithm
141
142 Input: Text string
143 Character count
144
145 Output: revised character count
146
147 Note: This is a very simplistic function. In effect it restricts
148 the action of the algorithm to the first paragraph in the input
149 where a paragraph ends at the end of the first block separator
150 or at the end of the input text.
151
152 ------------------------------------------------------------------------*/
153
resolveParagraphs(WORD * types,int cch)154 static int resolveParagraphs(WORD *types, int cch)
155 {
156 /* skip characters not of type B */
157 int ich = 0;
158 for(; ich < cch && types[ich] != B; ich++);
159 /* stop after first B, make it a BN for use in the next steps */
160 if (ich < cch && types[ich] == B)
161 types[ich++] = BN;
162 return ich;
163 }
164
165 /* REORDER */
166 /*------------------------------------------------------------------------
167 Function: resolveLines
168
169 Breaks a paragraph into lines
170
171 Input: Array of line break flags
172 Character count
173 In/Out: Array of characters
174
175 Returns the count of characters on the first line
176
177 Note: This function only breaks lines at hard line breaks. Other
178 line breaks can be passed in. If pbrk[n] is TRUE, then a break
179 occurs after the character in pszInput[n]. Breaks before the first
180 character are not allowed.
181 ------------------------------------------------------------------------*/
resolveLines(LPCWSTR pszInput,const BOOL * pbrk,int cch)182 static int resolveLines(LPCWSTR pszInput, const BOOL * pbrk, int cch)
183 {
184 /* skip characters not of type LS */
185 int ich = 0;
186 for(; ich < cch; ich++)
187 {
188 if (pszInput[ich] == (WCHAR)'\n' || (pbrk && pbrk[ich]))
189 {
190 ich++;
191 break;
192 }
193 }
194
195 return ich;
196 }
197
198 /*------------------------------------------------------------------------
199 Function: resolveWhiteSpace
200
201 Resolves levels for WS and S
202 Implements rule L1 of the Unicode bidi Algorithm.
203
204 Input: Base embedding level
205 Character count
206 Array of direction classes (for one line of text)
207
208 In/Out: Array of embedding levels (for one line of text)
209
210 Note: this should be applied a line at a time. The default driver
211 code supplied in this file assumes a single line of text; for
212 a real implementation, cch and the initial pointer values
213 would have to be adjusted.
214 ------------------------------------------------------------------------*/
resolveWhitespace(int baselevel,const WORD * pcls,BYTE * plevel,int cch)215 static void resolveWhitespace(int baselevel, const WORD *pcls, BYTE *plevel, int cch)
216 {
217 int cchrun = 0;
218 BYTE oldlevel = baselevel;
219
220 int ich = 0;
221 for (; ich < cch; ich++)
222 {
223 switch(pcls[ich])
224 {
225 default:
226 cchrun = 0; /* any other character breaks the run */
227 break;
228 case WS:
229 cchrun++;
230 break;
231
232 case RLE:
233 case LRE:
234 case LRO:
235 case RLO:
236 case PDF:
237 case LRI:
238 case RLI:
239 case FSI:
240 case PDI:
241 case BN:
242 plevel[ich] = oldlevel;
243 cchrun++;
244 break;
245
246 case S:
247 case B:
248 /* reset levels for WS before eot */
249 SetDeferredRun(plevel, cchrun, ich, baselevel);
250 cchrun = 0;
251 plevel[ich] = baselevel;
252 break;
253 }
254 oldlevel = plevel[ich];
255 }
256 /* reset level before eot */
257 SetDeferredRun(plevel, cchrun, ich, baselevel);
258 }
259
260 /*------------------------------------------------------------------------
261 Function: BidiLines
262
263 Implements the Line-by-Line phases of the Unicode Bidi Algorithm
264
265 Input: Count of characters
266 Array of character directions
267
268 Inp/Out: Input text
269 Array of levels
270
271 ------------------------------------------------------------------------*/
BidiLines(int baselevel,LPWSTR pszOutLine,LPCWSTR pszLine,const WORD * pclsLine,BYTE * plevelLine,int cchPara,const BOOL * pbrk)272 static void BidiLines(int baselevel, LPWSTR pszOutLine, LPCWSTR pszLine, const WORD * pclsLine,
273 BYTE * plevelLine, int cchPara, const BOOL * pbrk)
274 {
275 int cchLine = 0;
276 int done = 0;
277 int *run;
278
279 run = HeapAlloc(GetProcessHeap(), 0, cchPara * sizeof(int));
280 if (!run)
281 {
282 WARN("Out of memory\n");
283 return;
284 }
285
286 do
287 {
288 /* break lines at LS */
289 cchLine = resolveLines(pszLine, pbrk, cchPara);
290
291 /* resolve whitespace */
292 resolveWhitespace(baselevel, pclsLine, plevelLine, cchLine);
293
294 if (pszOutLine)
295 {
296 int i;
297 /* reorder each line in place */
298 ScriptLayout(cchLine, plevelLine, NULL, run);
299 for (i = 0; i < cchLine; i++)
300 pszOutLine[done+run[i]] = pszLine[i];
301 }
302
303 pszLine += cchLine;
304 plevelLine += cchLine;
305 pbrk += pbrk ? cchLine : 0;
306 pclsLine += cchLine;
307 cchPara -= cchLine;
308 done += cchLine;
309
310 } while (cchPara);
311
312 HeapFree(GetProcessHeap(), 0, run);
313 }
314
315 /*************************************************************
316 * BIDI_Reorder
317 *
318 * Returns TRUE if reordering was required and done.
319 */
BIDI_Reorder(HDC hDC,LPCWSTR lpString,INT uCount,DWORD dwFlags,DWORD dwWineGCP_Flags,LPWSTR lpOutString,INT uCountOut,UINT * lpOrder,WORD ** lpGlyphs,INT * cGlyphs)320 BOOL BIDI_Reorder(
321 HDC hDC, /*[in] Display DC */
322 LPCWSTR lpString, /* [in] The string for which information is to be returned */
323 INT uCount, /* [in] Number of WCHARs in string. */
324 DWORD dwFlags, /* [in] GetCharacterPlacement compatible flags specifying how to process the string */
325 DWORD dwWineGCP_Flags, /* [in] Wine internal flags - Force paragraph direction */
326 LPWSTR lpOutString, /* [out] Reordered string */
327 INT uCountOut, /* [in] Size of output buffer */
328 UINT *lpOrder, /* [out] Logical -> Visual order map */
329 WORD **lpGlyphs, /* [out] reordered, mirrored, shaped glyphs to display */
330 INT *cGlyphs /* [out] number of glyphs generated */
331 )
332 {
333 WORD *chartype;
334 BYTE *levels;
335 INT i, done;
336 unsigned glyph_i;
337 BOOL is_complex;
338
339 int maxItems;
340 int nItems;
341 SCRIPT_CONTROL Control;
342 SCRIPT_STATE State;
343 SCRIPT_ITEM *pItems;
344 HRESULT res;
345 SCRIPT_CACHE psc = NULL;
346 WORD *run_glyphs = NULL;
347 WORD *pwLogClust = NULL;
348 SCRIPT_VISATTR *psva = NULL;
349 DWORD cMaxGlyphs = 0;
350 BOOL doGlyphs = TRUE;
351
352 TRACE("%s, %d, 0x%08x lpOutString=%p, lpOrder=%p\n",
353 debugstr_wn(lpString, uCount), uCount, dwFlags,
354 lpOutString, lpOrder);
355
356 memset(&Control, 0, sizeof(Control));
357 memset(&State, 0, sizeof(State));
358 if (lpGlyphs)
359 *lpGlyphs = NULL;
360
361 if (!(dwFlags & GCP_REORDER))
362 {
363 FIXME("Asked to reorder without reorder flag set\n");
364 return FALSE;
365 }
366
367 if (lpOutString && uCountOut < uCount)
368 {
369 FIXME("lpOutString too small\n");
370 return FALSE;
371 }
372
373 chartype = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(WORD));
374 if (!chartype)
375 {
376 WARN("Out of memory\n");
377 return FALSE;
378 }
379
380 if (lpOutString)
381 memcpy(lpOutString, lpString, uCount * sizeof(WCHAR));
382
383 is_complex = FALSE;
384 for (i = 0; i < uCount && !is_complex; i++)
385 {
386 if ((lpString[i] >= 0x900 && lpString[i] <= 0xfff) ||
387 (lpString[i] >= 0x1cd0 && lpString[i] <= 0x1cff) ||
388 (lpString[i] >= 0xa840 && lpString[i] <= 0xa8ff))
389 is_complex = TRUE;
390 }
391
392 /* Verify reordering will be required */
393 if ((WINE_GCPW_FORCE_RTL == (dwWineGCP_Flags&WINE_GCPW_DIR_MASK)) ||
394 ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_RTL))
395 State.uBidiLevel = 1;
396 else if (!is_complex)
397 {
398 done = 1;
399 classify(lpString, chartype, uCount);
400 for (i = 0; i < uCount; i++)
401 switch (chartype[i])
402 {
403 case R:
404 case AL:
405 case RLE:
406 case RLO:
407 done = 0;
408 break;
409 }
410 if (done)
411 {
412 HeapFree(GetProcessHeap(), 0, chartype);
413 if (lpOrder)
414 {
415 for (i = 0; i < uCount; i++)
416 lpOrder[i] = i;
417 }
418 return TRUE;
419 }
420 }
421
422 levels = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(BYTE));
423 if (!levels)
424 {
425 WARN("Out of memory\n");
426 HeapFree(GetProcessHeap(), 0, chartype);
427 return FALSE;
428 }
429
430 maxItems = 5;
431 pItems = HeapAlloc(GetProcessHeap(),0, maxItems * sizeof(SCRIPT_ITEM));
432 if (!pItems)
433 {
434 WARN("Out of memory\n");
435 HeapFree(GetProcessHeap(), 0, chartype);
436 HeapFree(GetProcessHeap(), 0, levels);
437 return FALSE;
438 }
439
440 if (lpGlyphs)
441 {
442 #ifdef __REACTOS__
443 /* ReactOS r57677 and r57679 */
444 cMaxGlyphs = 3 * uCount / 2 + 16;
445 #else
446 cMaxGlyphs = 1.5 * uCount + 16;
447 #endif
448 run_glyphs = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * cMaxGlyphs);
449 if (!run_glyphs)
450 {
451 WARN("Out of memory\n");
452 HeapFree(GetProcessHeap(), 0, chartype);
453 HeapFree(GetProcessHeap(), 0, levels);
454 HeapFree(GetProcessHeap(), 0, pItems);
455 return FALSE;
456 }
457 pwLogClust = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * uCount);
458 if (!pwLogClust)
459 {
460 WARN("Out of memory\n");
461 HeapFree(GetProcessHeap(), 0, chartype);
462 HeapFree(GetProcessHeap(), 0, levels);
463 HeapFree(GetProcessHeap(), 0, pItems);
464 HeapFree(GetProcessHeap(), 0, run_glyphs);
465 return FALSE;
466 }
467 psva = HeapAlloc(GetProcessHeap(),0,sizeof(SCRIPT_VISATTR) * uCount);
468 if (!psva)
469 {
470 WARN("Out of memory\n");
471 HeapFree(GetProcessHeap(), 0, chartype);
472 HeapFree(GetProcessHeap(), 0, levels);
473 HeapFree(GetProcessHeap(), 0, pItems);
474 HeapFree(GetProcessHeap(), 0, run_glyphs);
475 HeapFree(GetProcessHeap(), 0, pwLogClust);
476 return FALSE;
477 }
478 }
479
480 done = 0;
481 glyph_i = 0;
482 while (done < uCount)
483 {
484 INT j;
485 classify(lpString + done, chartype, uCount - done);
486 /* limit text to first block */
487 i = resolveParagraphs(chartype, uCount - done);
488 for (j = 0; j < i; ++j)
489 switch(chartype[j])
490 {
491 case B:
492 case S:
493 case WS:
494 case ON: chartype[j] = NI;
495 default: continue;
496 }
497
498 if ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_RTL)
499 State.uBidiLevel = 1;
500 else if ((dwWineGCP_Flags&WINE_GCPW_DIR_MASK) == WINE_GCPW_LOOSE_LTR)
501 State.uBidiLevel = 0;
502
503 if (dwWineGCP_Flags & WINE_GCPW_LOOSE_MASK)
504 {
505 for (j = 0; j < i; ++j)
506 if (chartype[j] == L)
507 {
508 State.uBidiLevel = 0;
509 break;
510 }
511 else if (chartype[j] == R || chartype[j] == AL)
512 {
513 State.uBidiLevel = 1;
514 break;
515 }
516 }
517
518 res = ScriptItemize(lpString + done, i, maxItems, &Control, &State, pItems, &nItems);
519 while (res == E_OUTOFMEMORY)
520 {
521 maxItems = maxItems * 2;
522 pItems = HeapReAlloc(GetProcessHeap(), 0, pItems, sizeof(SCRIPT_ITEM) * maxItems);
523 if (!pItems)
524 {
525 WARN("Out of memory\n");
526 HeapFree(GetProcessHeap(), 0, chartype);
527 HeapFree(GetProcessHeap(), 0, levels);
528 HeapFree(GetProcessHeap(), 0, run_glyphs);
529 HeapFree(GetProcessHeap(), 0, pwLogClust);
530 HeapFree(GetProcessHeap(), 0, psva);
531 return FALSE;
532 }
533 res = ScriptItemize(lpString + done, i, maxItems, &Control, &State, pItems, &nItems);
534 }
535
536 if (lpOutString || lpOrder)
537 for (j = 0; j < nItems; j++)
538 {
539 int k;
540 for (k = pItems[j].iCharPos; k < pItems[j+1].iCharPos; k++)
541 levels[k] = pItems[j].a.s.uBidiLevel;
542 }
543
544 if (lpOutString)
545 {
546 /* assign directional types again, but for WS, S this time */
547 classify(lpString + done, chartype, i);
548
549 BidiLines(State.uBidiLevel, lpOutString + done, lpString + done,
550 chartype, levels, i, 0);
551 }
552
553 if (lpOrder)
554 {
555 int k, lastgood;
556 for (j = lastgood = 0; j < i; ++j)
557 if (levels[j] != levels[lastgood])
558 {
559 --j;
560 if (odd(levels[lastgood]))
561 for (k = j; k >= lastgood; --k)
562 lpOrder[done + k] = done + j - k;
563 else
564 for (k = lastgood; k <= j; ++k)
565 lpOrder[done + k] = done + k;
566 lastgood = ++j;
567 }
568 if (odd(levels[lastgood]))
569 for (k = j - 1; k >= lastgood; --k)
570 lpOrder[done + k] = done + j - 1 - k;
571 else
572 for (k = lastgood; k < j; ++k)
573 lpOrder[done + k] = done + k;
574 }
575
576 if (lpGlyphs && doGlyphs)
577 {
578 BYTE *runOrder;
579 int *visOrder;
580 SCRIPT_ITEM *curItem;
581
582 runOrder = HeapAlloc(GetProcessHeap(), 0, maxItems * sizeof(*runOrder));
583 visOrder = HeapAlloc(GetProcessHeap(), 0, maxItems * sizeof(*visOrder));
584 if (!runOrder || !visOrder)
585 {
586 WARN("Out of memory\n");
587 HeapFree(GetProcessHeap(), 0, runOrder);
588 HeapFree(GetProcessHeap(), 0, visOrder);
589 HeapFree(GetProcessHeap(), 0, chartype);
590 HeapFree(GetProcessHeap(), 0, levels);
591 HeapFree(GetProcessHeap(), 0, pItems);
592 HeapFree(GetProcessHeap(), 0, psva);
593 HeapFree(GetProcessHeap(), 0, pwLogClust);
594 return FALSE;
595 }
596
597 for (j = 0; j < nItems; j++)
598 runOrder[j] = pItems[j].a.s.uBidiLevel;
599
600 ScriptLayout(nItems, runOrder, visOrder, NULL);
601
602 for (j = 0; j < nItems; j++)
603 {
604 int k;
605 int cChars,cOutGlyphs;
606 curItem = &pItems[visOrder[j]];
607
608 cChars = pItems[visOrder[j]+1].iCharPos - curItem->iCharPos;
609
610 res = ScriptShape(hDC, &psc, lpString + done + curItem->iCharPos, cChars, cMaxGlyphs, &curItem->a, run_glyphs, pwLogClust, psva, &cOutGlyphs);
611 while (res == E_OUTOFMEMORY)
612 {
613 cMaxGlyphs *= 2;
614 run_glyphs = HeapReAlloc(GetProcessHeap(), 0, run_glyphs, sizeof(WORD) * cMaxGlyphs);
615 if (!run_glyphs)
616 {
617 WARN("Out of memory\n");
618 HeapFree(GetProcessHeap(), 0, runOrder);
619 HeapFree(GetProcessHeap(), 0, visOrder);
620 HeapFree(GetProcessHeap(), 0, chartype);
621 HeapFree(GetProcessHeap(), 0, levels);
622 HeapFree(GetProcessHeap(), 0, pItems);
623 HeapFree(GetProcessHeap(), 0, psva);
624 HeapFree(GetProcessHeap(), 0, pwLogClust);
625 HeapFree(GetProcessHeap(), 0, *lpGlyphs);
626 ScriptFreeCache(&psc);
627 *lpGlyphs = NULL;
628 return FALSE;
629 }
630 res = ScriptShape(hDC, &psc, lpString + done + curItem->iCharPos, cChars, cMaxGlyphs, &curItem->a, run_glyphs, pwLogClust, psva, &cOutGlyphs);
631 }
632 if (res)
633 {
634 if (res == USP_E_SCRIPT_NOT_IN_FONT)
635 TRACE("Unable to shape with currently selected font\n");
636 else
637 FIXME("Unable to shape string (%x)\n",res);
638 j = nItems;
639 doGlyphs = FALSE;
640 HeapFree(GetProcessHeap(), 0, *lpGlyphs);
641 *lpGlyphs = NULL;
642 }
643 else
644 {
645 if (*lpGlyphs)
646 *lpGlyphs = HeapReAlloc(GetProcessHeap(), 0, *lpGlyphs, sizeof(WORD) * (glyph_i + cOutGlyphs));
647 else
648 *lpGlyphs = HeapAlloc(GetProcessHeap(), 0, sizeof(WORD) * (glyph_i + cOutGlyphs));
649 for (k = 0; k < cOutGlyphs; k++)
650 (*lpGlyphs)[glyph_i+k] = run_glyphs[k];
651 glyph_i += cOutGlyphs;
652 }
653 }
654 HeapFree(GetProcessHeap(), 0, runOrder);
655 HeapFree(GetProcessHeap(), 0, visOrder);
656 }
657
658 done += i;
659 }
660 if (cGlyphs)
661 *cGlyphs = glyph_i;
662
663 HeapFree(GetProcessHeap(), 0, chartype);
664 HeapFree(GetProcessHeap(), 0, levels);
665 HeapFree(GetProcessHeap(), 0, pItems);
666 HeapFree(GetProcessHeap(), 0, run_glyphs);
667 HeapFree(GetProcessHeap(), 0, pwLogClust);
668 HeapFree(GetProcessHeap(), 0, psva);
669 ScriptFreeCache(&psc);
670 return TRUE;
671 }
672