msi: Add tests for the MSIMODIFY_REFRESH command.
[wine] / dlls / riched20 / writer.c
1 /*
2  * RichEdit - RTF writer module
3  *
4  * Copyright 2005 by Phil Krylov
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include "editor.h"
25 #include "rtf.h"
26
27 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
28
29
30 static BOOL
31 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
32
33
34 static ME_OutStream*
35 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
36 {
37   ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream);
38   pStream->stream = stream;
39   pStream->stream->dwError = 0;
40   pStream->pos = 0;
41   pStream->written = 0;
42   pStream->nFontTblLen = 0;
43   pStream->nColorTblLen = 1;
44   return pStream;
45 }
46
47
48 static BOOL
49 ME_StreamOutFlush(ME_OutStream *pStream)
50 {
51   LONG nStart = 0;
52   LONG nWritten = 0;
53   LONG nRemaining = 0;
54   EDITSTREAM *stream = pStream->stream;
55
56   do {
57     TRACE("sending %u bytes\n", pStream->pos - nStart);
58     /* Some apps seem not to set *pcb unless a problem arises, relying
59       on initial random nWritten value, which is usually >STREAMOUT_BUFFER_SIZE */
60     nRemaining = pStream->pos - nStart;
61     nWritten = 0xDEADBEEF;
62     stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer + nStart,
63                                           pStream->pos - nStart, &nWritten);
64     TRACE("error=%u written=%u\n", stream->dwError, nWritten);
65     if (nWritten > (pStream->pos - nStart) || nWritten<0) {
66       FIXME("Invalid returned written size *pcb: 0x%x (%d) instead of %d\n", 
67             (unsigned)nWritten, nWritten, nRemaining);
68       nWritten = nRemaining;
69     }
70     if (nWritten == 0 || stream->dwError)
71       return FALSE;
72     pStream->written += nWritten;
73     nStart += nWritten;
74   } while (nStart < pStream->pos);
75   pStream->pos = 0;
76   return TRUE;
77 }
78
79
80 static LONG
81 ME_StreamOutFree(ME_OutStream *pStream)
82 {
83   LONG written = pStream->written;
84   TRACE("total length = %u\n", written);
85
86   FREE_OBJ(pStream);
87   return written;
88 }
89
90
91 static BOOL
92 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
93 {
94   while (len) {
95     int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
96     int fit = min(space, len);
97
98     TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
99     memmove(pStream->buffer + pStream->pos, buffer, fit);
100     len -= fit;
101     buffer += fit;
102     pStream->pos += fit;
103     if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
104       if (!ME_StreamOutFlush(pStream))
105         return FALSE;
106     }
107   }
108   return TRUE;
109 }
110
111
112 static BOOL
113 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
114 {
115   char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
116   int len;
117   va_list valist;
118
119   va_start(valist, format);
120   len = vsnprintf(string, sizeof(string), format, valist);
121   va_end(valist);
122   
123   return ME_StreamOutMove(pStream, string, len);
124 }
125
126
127 static BOOL
128 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
129 {
130   const char *cCharSet = NULL;
131   UINT nCodePage;
132   LANGID language;
133   BOOL success;
134   
135   if (dwFormat & SF_USECODEPAGE) {
136     CPINFOEXW info;
137     
138     switch (HIWORD(dwFormat)) {
139       case CP_ACP:
140         cCharSet = "ansi";
141         nCodePage = GetACP();
142         break;
143       case CP_OEMCP:
144         nCodePage = GetOEMCP();
145         if (nCodePage == 437)
146           cCharSet = "pc";
147         else if (nCodePage == 850)
148           cCharSet = "pca";
149         else
150           cCharSet = "ansi";
151         break;
152       case CP_UTF8:
153         nCodePage = CP_UTF8;
154         break;
155       default:
156         if (HIWORD(dwFormat) == CP_MACCP) {
157           cCharSet = "mac";
158           nCodePage = 10000; /* MacRoman */
159         } else {
160           cCharSet = "ansi";
161           nCodePage = 1252; /* Latin-1 */
162         }
163         if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
164           nCodePage = info.CodePage;
165     }
166   } else {
167     cCharSet = "ansi";
168     /* TODO: If the original document contained an \ansicpg value, retain it.
169      * Otherwise, M$ richedit emits a codepage number determined from the
170      * charset of the default font here. Anyway, this value is not used by
171      * the reader... */
172     nCodePage = GetACP();
173   }
174   if (nCodePage == CP_UTF8)
175     success = ME_StreamOutPrint(pStream, "{\\urtf");
176   else
177     success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
178
179   if (!success)
180     return FALSE;
181
182   pStream->nDefaultCodePage = nCodePage;
183   
184   /* FIXME: This should be a document property */
185   /* TODO: handle SFF_PLAINRTF */
186   language = GetUserDefaultLangID(); 
187   if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
188     return FALSE;
189
190   /* FIXME: This should be a document property */
191   pStream->nDefaultFont = 0;
192
193   return TRUE;
194 }
195
196
197 static BOOL
198 ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun, const ME_DisplayItem *pLastRun)
199 {
200   ME_DisplayItem *item = pFirstRun;
201   ME_FontTableItem *table = pStream->fonttbl;
202   int i;
203   
204   do {
205     CHARFORMAT2W *fmt = &item->member.run.style->fmt;
206     COLORREF crColor;
207
208     if (fmt->dwMask & CFM_FACE) {
209       WCHAR *face = fmt->szFaceName;
210       BYTE bCharSet = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
211   
212       for (i = 0; i < pStream->nFontTblLen; i++)
213         if (table[i].bCharSet == bCharSet
214             && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
215           break;
216       if (i == pStream->nFontTblLen) {
217         table[i].bCharSet = bCharSet;
218         table[i].szFaceName = face;
219         pStream->nFontTblLen++;
220       }
221     }
222     
223     if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) {
224       crColor = fmt->crTextColor;
225       for (i = 1; i < pStream->nColorTblLen; i++)
226         if (pStream->colortbl[i] == crColor)
227           break;
228       if (i == pStream->nColorTblLen) {
229         pStream->colortbl[i] = crColor;
230         pStream->nColorTblLen++;
231       }
232     }
233     if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
234       crColor = fmt->crBackColor;
235       for (i = 1; i < pStream->nColorTblLen; i++)
236         if (pStream->colortbl[i] == crColor)
237           break;
238       if (i == pStream->nColorTblLen) {
239         pStream->colortbl[i] = crColor;
240         pStream->nColorTblLen++;
241       }
242     }
243
244     if (item == pLastRun)
245       break;
246     item = ME_FindItemFwd(item, diRun);
247   } while (item);
248         
249   if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
250     return FALSE;
251   
252   for (i = 0; i < pStream->nFontTblLen; i++) {
253     if (table[i].bCharSet != DEFAULT_CHARSET) {
254       if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
255         return FALSE;
256     } else {
257       if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
258         return FALSE;
259     }
260     if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
261       return FALSE;
262     if (!ME_StreamOutPrint(pStream, ";}\r\n"))
263       return FALSE;
264   }
265   if (!ME_StreamOutPrint(pStream, "}"))
266     return FALSE;
267
268   /* Output colors table if not empty */
269   if (pStream->nColorTblLen > 1) {
270     if (!ME_StreamOutPrint(pStream, "{\\colortbl;"))
271       return FALSE;
272     for (i = 1; i < pStream->nColorTblLen; i++) {
273       if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;",
274                              pStream->colortbl[i] & 0xFF,
275                              (pStream->colortbl[i] >> 8) & 0xFF,
276                              (pStream->colortbl[i] >> 16) & 0xFF))
277         return FALSE;
278     }
279     if (!ME_StreamOutPrint(pStream, "}"))
280       return FALSE;
281   }
282
283   return TRUE;
284 }
285
286
287 static BOOL
288 ME_StreamOutRTFParaProps(ME_OutStream *pStream, const ME_DisplayItem *para)
289 {
290   PARAFORMAT2 *fmt = para->member.para.pFmt;
291   char props[STREAMOUT_BUFFER_SIZE] = "";
292   int i;
293
294   if (para->member.para.pCells)
295   {
296     ME_TableCell *cell = para->member.para.pCells;
297     
298     if (!ME_StreamOutPrint(pStream, "\\trowd"))
299       return FALSE;
300     do {
301       sprintf(props, "\\cellx%d", cell->nRightBoundary);
302       if (!ME_StreamOutPrint(pStream, props))
303         return FALSE;
304       cell = cell->next;
305     } while (cell);
306     props[0] = '\0';
307   }
308   
309   /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
310   if (!ME_StreamOutPrint(pStream, "\\pard"))
311     return FALSE;
312
313   if (para->member.para.bTable)
314     strcat(props, "\\intbl");
315   
316   /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
317    * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
318    * set very different from the documentation.
319    * (Tested with RichEdit 5.50.25.0601) */
320   
321   if (fmt->dwMask & PFM_ALIGNMENT) {
322     switch (fmt->wAlignment) {
323       case PFA_LEFT:
324         /* Default alignment: not emitted */
325         break;
326       case PFA_RIGHT:
327         strcat(props, "\\qr");
328         break;
329       case PFA_CENTER:
330         strcat(props, "\\qc");
331         break;
332       case PFA_JUSTIFY:
333         strcat(props, "\\qj");
334         break;
335     }
336   }
337   
338   if (fmt->dwMask & PFM_LINESPACING) {
339     /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
340      * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
341     switch (fmt->bLineSpacingRule) {
342       case 0: /* Single spacing */
343         strcat(props, "\\sl-240\\slmult1");
344         break;
345       case 1: /* 1.5 spacing */
346         strcat(props, "\\sl-360\\slmult1");
347         break;
348       case 2: /* Double spacing */
349         strcat(props, "\\sl-480\\slmult1");
350         break;
351       case 3:
352         sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
353         break;
354       case 4:
355         sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
356         break;
357       case 5:
358         sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
359         break;
360     }
361   }
362
363   if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
364     strcat(props, "\\hyph0");
365   if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
366     strcat(props, "\\keep");
367   if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
368     strcat(props, "\\keepn");
369   if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
370     strcat(props, "\\noline");
371   if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
372     strcat(props, "\\nowidctlpar");
373   if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
374     strcat(props, "\\pagebb");
375   if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
376     strcat(props, "\\rtlpar");
377   if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
378     strcat(props, "\\sbys");
379   if (fmt->dwMask & PFM_TABLE && fmt->dwMask & PFE_TABLE)
380     strcat(props, "\\intbl");
381   
382   if (fmt->dwMask & PFM_OFFSET)
383     sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
384   if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT)
385     sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
386   if (fmt->dwMask & PFM_RIGHTINDENT)
387     sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
388   if (fmt->dwMask & PFM_SPACEAFTER)
389     sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
390   if (fmt->dwMask & PFM_SPACEBEFORE)
391     sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
392   if (fmt->dwMask & PFM_STYLE)
393     sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
394
395   if (fmt->dwMask & PFM_TABSTOPS) {
396     static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
397     
398     for (i = 0; i < fmt->cTabCount; i++) {
399       switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
400         case 1:
401           strcat(props, "\\tqc");
402           break;
403         case 2:
404           strcat(props, "\\tqr");
405           break;
406         case 3:
407           strcat(props, "\\tqdec");
408           break;
409         case 4:
410           /* Word bar tab (vertical bar). Handled below */
411           break;
412       }
413       if (fmt->rgxTabs[i] >> 28 <= 5)
414         strcat(props, leader[fmt->rgxTabs[i] >> 28]);
415       sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
416     }
417   }
418     
419   
420   if (fmt->dwMask & PFM_SHADING) {
421     static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
422                                      "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
423                                      "\\bghoriz", "\\bgvert", "\\bgfdiag",
424                                      "\\bgbdiag", "\\bgcross", "\\bgdcross",
425                                      "", "", "" };
426     if (fmt->wShadingWeight)
427       sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
428     if (fmt->wShadingStyle & 0xF)
429       strcat(props, style[fmt->wShadingStyle & 0xF]);
430     sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
431             (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
432   }
433   
434   if (*props && !ME_StreamOutPrint(pStream, props))
435     return FALSE;
436
437   return TRUE;
438 }
439
440
441 static BOOL
442 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
443 {
444   char props[STREAMOUT_BUFFER_SIZE] = "";
445   int i;
446
447   if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
448     strcat(props, "\\caps");
449   if (fmt->dwMask & CFM_ANIMATION)
450     sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
451   if (fmt->dwMask & CFM_BACKCOLOR) {
452     if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
453       for (i = 1; i < pStream->nColorTblLen; i++)
454         if (pStream->colortbl[i] == fmt->crBackColor) {
455           sprintf(props + strlen(props), "\\cb%u", i);
456           break;
457         }
458     }
459   }
460   if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
461     strcat(props, "\\b");
462   if (fmt->dwMask & CFM_COLOR) {
463     if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
464       for (i = 1; i < pStream->nColorTblLen; i++)
465         if (pStream->colortbl[i] == fmt->crTextColor) {
466           sprintf(props + strlen(props), "\\cf%u", i);
467           break;
468         }
469     }
470   }
471   /* TODO: CFM_DISABLED */
472   if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
473     strcat(props, "\\embo");
474   if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
475     strcat(props, "\\v");
476   if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
477     strcat(props, "\\impr");
478   if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
479     strcat(props, "\\i");
480   if (fmt->dwMask & CFM_KERNING)
481     sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
482   if (fmt->dwMask & CFM_LCID) {
483     /* TODO: handle SFF_PLAINRTF */
484     if (LOWORD(fmt->lcid) == 1024)
485       strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
486     else
487       sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
488   }
489   /* CFM_LINK is not streamed out by M$ */
490   if (fmt->dwMask & CFM_OFFSET) {
491     if (fmt->yOffset >= 0)
492       sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
493     else
494       sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
495   }
496   if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
497     strcat(props, "\\outl");
498   if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
499     strcat(props, "\\protect");
500   /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
501   if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
502     strcat(props, "\\shad");
503   if (fmt->dwMask & CFM_SIZE)
504     sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
505   if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
506     strcat(props, "\\scaps");
507   if (fmt->dwMask & CFM_SPACING)
508     sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
509   if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
510     strcat(props, "\\strike");
511   if (fmt->dwMask & CFM_STYLE) {
512     sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
513     /* TODO: emit style contents here */
514   }
515   if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
516     if (fmt->dwEffects & CFE_SUBSCRIPT)
517       strcat(props, "\\sub");
518     else if (fmt->dwEffects & CFE_SUPERSCRIPT)
519       strcat(props, "\\super");
520   }
521   if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
522     if (fmt->dwMask & CFM_UNDERLINETYPE)
523       switch (fmt->bUnderlineType) {
524         case CFU_CF1UNDERLINE:
525         case CFU_UNDERLINE:
526           strcat(props, "\\ul");
527           break;
528         case CFU_UNDERLINEDOTTED:
529           strcat(props, "\\uld");
530           break;
531         case CFU_UNDERLINEDOUBLE:
532           strcat(props, "\\uldb");
533           break;
534         case CFU_UNDERLINEWORD:
535           strcat(props, "\\ulw");
536           break;
537         case CFU_UNDERLINENONE:
538         default:
539           strcat(props, "\\ul0");
540           break;
541       }
542     else if (fmt->dwEffects & CFE_UNDERLINE)
543       strcat(props, "\\ul");
544   }
545   /* FIXME: How to emit CFM_WEIGHT? */
546   
547   if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
548     WCHAR *szFaceName;
549     
550     if (fmt->dwMask & CFM_FACE)
551       szFaceName = fmt->szFaceName;
552     else
553       szFaceName = pStream->fonttbl[0].szFaceName;
554     for (i = 0; i < pStream->nFontTblLen; i++) {
555       if (szFaceName == pStream->fonttbl[i].szFaceName
556           || !lstrcmpW(szFaceName, pStream->fonttbl[i].szFaceName))
557         if (!(fmt->dwMask & CFM_CHARSET)
558             || fmt->bCharSet == pStream->fonttbl[i].bCharSet)
559           break;
560     }
561     if (i < pStream->nFontTblLen)
562     {
563       if (i != pStream->nDefaultFont)
564         sprintf(props + strlen(props), "\\f%u", i);
565
566       /* In UTF-8 mode, charsets/codepages are not used */
567       if (pStream->nDefaultCodePage != CP_UTF8)
568       {
569         if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
570           pStream->nCodePage = pStream->nDefaultCodePage;
571         else
572           pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
573       }
574     }
575   }
576   if (*props)
577     strcat(props, " ");
578   if (!ME_StreamOutPrint(pStream, props))
579     return FALSE;
580   return TRUE;
581 }
582
583
584 static BOOL
585 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
586 {
587   char buffer[STREAMOUT_BUFFER_SIZE];
588   int pos = 0;
589   int fit, nBytes, i;
590
591   if (nChars == -1)
592     nChars = lstrlenW(text);
593   
594   while (nChars) {
595     /* In UTF-8 mode, font charsets are not used. */
596     if (pStream->nDefaultCodePage == CP_UTF8) {
597       /* 6 is the maximum character length in UTF-8 */
598       fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
599       nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
600                                    STREAMOUT_BUFFER_SIZE, NULL, NULL);
601       nChars -= fit;
602       text += fit;
603       for (i = 0; i < nBytes; i++)
604         if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
605           if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
606             return FALSE;
607           pos = i;
608         }
609       if (pos < nBytes)
610         if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
611           return FALSE;
612       pos = 0;
613     } else if (*text < 128) {
614       if (*text == '{' || *text == '}' || *text == '\\')
615         buffer[pos++] = '\\';
616       buffer[pos++] = (char)(*text++);
617       nChars--;
618     } else {
619       BOOL unknown = FALSE;
620       char letter[3];
621
622       /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
623        * codepages including CP_SYMBOL for which the last parameter must be set
624        * to NULL for the function to succeed. But in Wine we need to care only
625        * about CP_SYMBOL */
626       nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
627                                    letter, 3, NULL,
628                                    (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
629       if (unknown)
630         pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
631       else if ((BYTE)*letter < 128) {
632         if (*letter == '{' || *letter == '}' || *letter == '\\')
633           buffer[pos++] = '\\';
634         buffer[pos++] = *letter;
635       } else {
636          for (i = 0; i < nBytes; i++)
637            pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
638       }
639       text++;
640       nChars--;
641     }
642     if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
643       if (!ME_StreamOutMove(pStream, buffer, pos))
644         return FALSE;
645       pos = 0;
646     }
647   }
648   return ME_StreamOutMove(pStream, buffer, pos);
649 }
650
651
652 static BOOL
653 ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream, int nStart, int nChars, int dwFormat)
654 {
655   ME_DisplayItem *p, *pEnd, *pPara;
656   int nOffset, nEndLen; 
657   
658   ME_RunOfsFromCharOfs(editor, nStart, &p, &nOffset);
659   ME_RunOfsFromCharOfs(editor, nStart+nChars, &pEnd, &nEndLen);
660   
661   pPara = ME_GetParagraph(p);
662   
663   if (!ME_StreamOutRTFHeader(pStream, dwFormat))
664     return FALSE;
665
666   if (!ME_StreamOutRTFFontAndColorTbl(pStream, p, pEnd))
667     return FALSE;
668   
669   /* TODO: stylesheet table */
670   
671   /* FIXME: maybe emit something smarter for the generator? */
672   if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0.????;}"))
673     return FALSE;
674
675   /* TODO: information group */
676
677   /* TODO: document formatting properties */
678
679   /* FIXME: We have only one document section */
680
681   /* TODO: section formatting properties */
682
683   if (!ME_StreamOutRTFParaProps(pStream, ME_GetParagraph(p)))
684     return FALSE;
685
686   while(1)
687   {
688     switch(p->type)
689     {
690       case diParagraph:
691         if (!ME_StreamOutRTFParaProps(pStream, p))
692           return FALSE;
693         pPara = p;
694         break;
695       case diRun:
696         if (p == pEnd && !nEndLen)
697           break;
698         TRACE("flags %xh\n", p->member.run.nFlags);
699         /* TODO: emit embedded objects */
700         if (p->member.run.nFlags & MERF_GRAPHICS)
701           FIXME("embedded objects are not handled\n");
702         if (p->member.run.nFlags & MERF_CELL) {
703           if (!ME_StreamOutPrint(pStream, "\\cell "))
704             return FALSE;
705           nChars--;
706         } else if (p->member.run.nFlags & MERF_ENDPARA) {
707           if (pPara->member.para.bTable) {
708             if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
709               return FALSE;
710           } else {
711             if (!ME_StreamOutPrint(pStream, "\r\n\\par"))
712               return FALSE;
713           }
714           nChars--;
715           if (editor->bEmulateVersion10 && nChars)
716             nChars--;
717         } else {
718           int nEnd;
719           
720           if (!ME_StreamOutPrint(pStream, "{"))
721             return FALSE;
722           TRACE("style %p\n", p->member.run.style);
723           if (!ME_StreamOutRTFCharProps(pStream, &p->member.run.style->fmt))
724             return FALSE;
725         
726           nEnd = (p == pEnd) ? nEndLen : ME_StrLen(p->member.run.strText);
727           if (!ME_StreamOutRTFText(pStream, p->member.run.strText->szData + nOffset, nEnd - nOffset))
728             return FALSE;
729           nOffset = 0;
730           if (!ME_StreamOutPrint(pStream, "}"))
731             return FALSE;
732         }
733         break;
734       default: /* we missed the last item */
735         assert(0);
736     }
737     if (p == pEnd)
738       break;
739     p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
740   }
741   if (!ME_StreamOutPrint(pStream, "}"))
742     return FALSE;
743   return TRUE;
744 }
745
746
747 static BOOL
748 ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream, int nStart, int nChars, DWORD dwFormat)
749 {
750   /* FIXME: use ME_RunOfsFromCharOfs */
751   ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart);
752   int nLen;
753   UINT nCodePage = CP_ACP;
754   char *buffer = NULL;
755   int nBufLen = 0;
756   BOOL success = TRUE;
757
758   if (!item)
759     return FALSE;
760    
761   if (dwFormat & SF_USECODEPAGE)
762     nCodePage = HIWORD(dwFormat);
763
764   /* TODO: Handle SF_TEXTIZED */
765   
766   while (success && nChars && item) {
767     nLen = ME_StrLen(item->member.run.strText) - nStart;
768     if (nLen > nChars)
769       nLen = nChars;
770
771     if (item->member.run.nFlags & MERF_ENDPARA) {
772       static const WCHAR szEOL[2] = { '\r', '\n' };
773       
774       if (dwFormat & SF_UNICODE)
775         success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
776       else
777         success = ME_StreamOutMove(pStream, "\r\n", 2);
778     } else {
779       if (dwFormat & SF_UNICODE)
780         success = ME_StreamOutMove(pStream, (const char *)(item->member.run.strText->szData + nStart),
781                                    sizeof(WCHAR) * nLen);
782       else {
783         int nSize;
784
785         nSize = WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
786                                     nLen, NULL, 0, NULL, NULL);
787         if (nSize > nBufLen) {
788           FREE_OBJ(buffer);
789           buffer = ALLOC_N_OBJ(char, nSize);
790           nBufLen = nSize;
791         }
792         WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
793                             nLen, buffer, nSize, NULL, NULL);
794         success = ME_StreamOutMove(pStream, buffer, nSize);
795       }
796     }
797     
798     nChars -= nLen;
799     if (editor->bEmulateVersion10 && nChars && item->member.run.nFlags & MERF_ENDPARA)
800       nChars--;
801     nStart = 0;
802     item = ME_FindItemFwd(item, diRun);
803   }
804   
805   FREE_OBJ(buffer);
806   return success;
807 }
808
809
810 LRESULT
811 ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat, int nStart, int nTo, EDITSTREAM *stream)
812 {
813   ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
814
815   if (nTo == -1)
816   {
817     nTo = ME_GetTextLength(editor);
818     /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
819     if (dwFormat & SF_RTF)
820       nTo++;
821   }
822   TRACE("from %d to %d\n", nStart, nTo);
823
824   if (dwFormat & SF_RTF)
825     ME_StreamOutRTF(editor, pStream, nStart, nTo - nStart, dwFormat);
826   else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
827     ME_StreamOutText(editor, pStream, nStart, nTo - nStart, dwFormat);
828   if (!pStream->stream->dwError)
829     ME_StreamOutFlush(pStream);
830   return ME_StreamOutFree(pStream);
831 }
832
833 LRESULT
834 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
835 {
836   int nStart, nTo;
837
838   if (dwFormat & SFF_SELECTION)
839     ME_GetSelection(editor, &nStart, &nTo);
840   else {
841     nStart = 0;
842     nTo = -1;
843   }
844   return ME_StreamOutRange(editor, dwFormat, nStart, nTo, stream);
845 }