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