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