Fixes for -Wmissing-declaration and -Wwrite-string warnings.
[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       sprintf(props+strlen(props), "\\tx%ld", fmt->rgxTabs[i]&0x00FFFFFF);
383     }
384   }
385     
386   
387   if (fmt->dwMask & PFM_SHADING) {
388     static const char *style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
389                                      "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
390                                      "\\bghoriz", "\\bgvert", "\\bgfdiag",
391                                      "\\bgbdiag", "\\bgcross", "\\bgdcross",
392                                      "", "", "" };
393     if (fmt->wShadingWeight)
394       sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
395     if (fmt->wShadingStyle & 0xF)
396       strcat(props, style[fmt->wShadingStyle & 0xF]);
397     sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
398             (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
399   }
400   
401   if (*props && !ME_StreamOutPrint(editor, props))
402     return FALSE;
403
404   return TRUE;
405 }
406
407
408 static BOOL
409 ME_StreamOutRTFCharProps(ME_TextEditor *editor, CHARFORMAT2W *fmt)
410 {
411   char props[STREAMOUT_BUFFER_SIZE] = "";
412   int i;
413
414   if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
415     strcat(props, "\\caps");
416   if (fmt->dwMask & CFM_ANIMATION)
417     sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
418   if (fmt->dwMask & CFM_BACKCOLOR) {
419     if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
420       for (i = 1; i < editor->pStream->nColorTblLen; i++)
421         if (editor->pStream->colortbl[i] == fmt->crBackColor) {
422           sprintf(props + strlen(props), "\\cb%u", i);
423           break;
424         }
425     }
426   }
427   if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
428     strcat(props, "\\b");
429   if (fmt->dwMask & CFM_COLOR) {
430     if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
431       for (i = 1; i < editor->pStream->nColorTblLen; i++)
432         if (editor->pStream->colortbl[i] == fmt->crTextColor) {
433           sprintf(props + strlen(props), "\\cf%u", i);
434           break;
435         }
436     }
437   }
438   /* TODO: CFM_DISABLED */
439   if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
440     strcat(props, "\\embo");
441   if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
442     strcat(props, "\\v");
443   if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
444     strcat(props, "\\impr");
445   if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
446     strcat(props, "\\i");
447   if (fmt->dwMask & CFM_KERNING)
448     sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
449   if (fmt->dwMask & CFM_LCID) {
450     /* TODO: handle SFF_PLAINRTF */
451     if (LOWORD(fmt->lcid) == 1024)
452       strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
453     else
454       sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
455   }
456   /* CFM_LINK is not streamed out by M$ */
457   if (fmt->dwMask & CFM_OFFSET) {
458     if (fmt->yOffset >= 0)
459       sprintf(props + strlen(props), "\\up%ld", fmt->yOffset);
460     else
461       sprintf(props + strlen(props), "\\dn%ld", -fmt->yOffset);
462   }
463   if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
464     strcat(props, "\\outl");
465   if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
466     strcat(props, "\\protect");
467   /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
468   if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
469     strcat(props, "\\shad");
470   if (fmt->dwMask & CFM_SIZE)
471     sprintf(props + strlen(props), "\\fs%ld", fmt->yHeight / 10);
472   if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
473     strcat(props, "\\scaps");
474   if (fmt->dwMask & CFM_SPACING)
475     sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
476   if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
477     strcat(props, "\\strike");
478   if (fmt->dwMask & CFM_STYLE) {
479     sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
480     /* TODO: emit style contents here */
481   }
482   if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
483     if (fmt->dwEffects & CFE_SUBSCRIPT)
484       strcat(props, "\\sub");
485     else if (fmt->dwEffects & CFE_SUPERSCRIPT)
486       strcat(props, "\\super");
487   }
488   if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
489     if (fmt->dwMask & CFM_UNDERLINETYPE)
490       switch (fmt->bUnderlineType) {
491         case CFU_CF1UNDERLINE:
492         case CFU_UNDERLINE:
493           strcat(props, "\\ul");
494           break;
495         case CFU_UNDERLINEDOTTED:
496           strcat(props, "\\uld");
497           break;
498         case CFU_UNDERLINEDOUBLE:
499           strcat(props, "\\uldb");
500           break;
501         case CFU_UNDERLINEWORD:
502           strcat(props, "\\ulw");
503           break;
504         case CFU_UNDERLINENONE:
505         default:
506           strcat(props, "\\ul0");
507           break;
508       }
509     else if (fmt->dwEffects & CFE_UNDERLINE)
510       strcat(props, "\\ul");
511   }
512   /* FIXME: How to emit CFM_WEIGHT? */
513   
514   if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
515     WCHAR *szFaceName;
516     
517     if (fmt->dwMask & CFM_FACE)
518       szFaceName = fmt->szFaceName;
519     else
520       szFaceName = editor->pStream->fonttbl[0].szFaceName;
521     for (i = 0; i < editor->pStream->nFontTblLen; i++) {
522       if (szFaceName == editor->pStream->fonttbl[i].szFaceName
523           || !lstrcmpW(szFaceName, editor->pStream->fonttbl[i].szFaceName))
524         if (!(fmt->dwMask & CFM_CHARSET)
525             || fmt->bCharSet == editor->pStream->fonttbl[i].bCharSet)
526           break;
527     }
528     if (i < editor->pStream->nFontTblLen)
529     {
530       if (i != editor->pStream->nDefaultFont)
531         sprintf(props + strlen(props), "\\f%u", i);
532
533       /* In UTF-8 mode, charsets/codepages are not used */
534       if (editor->pStream->nDefaultCodePage != CP_UTF8)
535       {
536         if (editor->pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
537           editor->pStream->nCodePage = editor->pStream->nDefaultCodePage;
538         else
539           editor->pStream->nCodePage = RTFCharSetToCodePage(NULL,
540                                                             editor->pStream->fonttbl[i].bCharSet);
541       }
542     }
543   }
544   if (*props)
545     strcat(props, " ");
546   if (!ME_StreamOutPrint(editor, props))
547     return FALSE;
548   return TRUE;
549 }
550
551
552 static BOOL
553 ME_StreamOutRTFText(ME_TextEditor *editor, WCHAR *text, LONG nChars)
554 {
555   char buffer[STREAMOUT_BUFFER_SIZE];
556   int pos = 0;
557   int fit, i;
558
559   if (nChars == -1)
560     nChars = lstrlenW(text);
561   
562   while (nChars) {
563     /* In UTF-8 mode, font charsets are not used. */
564     if (editor->pStream->nDefaultCodePage == CP_UTF8) {
565       /* 6 is the maximum character length in UTF-8 */
566       fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
567       WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer, STREAMOUT_BUFFER_SIZE,
568                           NULL, NULL);
569       nChars -= fit;
570       text += fit;
571       for (i = 0; buffer[i]; i++)
572         if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
573           if (!ME_StreamOutPrint(editor, "%.*s\\", i - pos, buffer + pos))
574             return FALSE;
575           pos = i;
576         }
577       if (!pos)
578         if (!ME_StreamOutPrint(editor, "%s", buffer + pos))
579           return FALSE;
580       pos = 0;
581     } else if (*text < 128) {
582       if (*text == '{' || *text == '}' || *text == '\\')
583         buffer[pos++] = '\\';
584       buffer[pos++] = (char)(*text++);
585       nChars--;
586     } else {
587       BOOL unknown = FALSE;
588       BYTE letter[3];
589       int nBytes, i;
590       
591       /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
592        * codepages including CP_SYMBOL for which the last parameter must be set
593        * to NULL for the function to succeed. But in Wine we need to care only
594        * about CP_SYMBOL */
595       nBytes = WideCharToMultiByte(editor->pStream->nCodePage, 0, text, 1,
596                                    letter, 3, NULL,
597                                    (editor->pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
598       if (unknown)
599         pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
600       else if (*letter < 128) {
601         if (*letter == '{' || *letter == '}' || *letter == '\\')
602           buffer[pos++] = '\\';
603         buffer[pos++] = *letter;
604       } else {
605          for (i = 0; i < nBytes; i++)
606            pos += sprintf(buffer + pos, "\\'%02x", letter[i]);
607       }
608       text++;
609       nChars--;
610     }
611     if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
612       if (!ME_StreamOutMove(editor, buffer, pos))
613         return FALSE;
614       pos = 0;
615     }
616   }
617   return ME_StreamOutMove(editor, buffer, pos);
618 }
619
620
621 static BOOL
622 ME_StreamOutRTF(ME_TextEditor *editor, int nStart, int nChars, int dwFormat)
623 {
624   ME_DisplayItem *p, *pEnd;
625   int nOffset, nEndLen;
626   ME_RunOfsFromCharOfs(editor, nStart, &p, &nOffset);
627   ME_RunOfsFromCharOfs(editor, nStart+nChars, &pEnd, &nEndLen);
628   
629   if (!ME_StreamOutRTFHeader(editor, dwFormat))
630     return FALSE;
631
632   if (!ME_StreamOutRTFFontAndColorTbl(editor, p, pEnd))
633     return FALSE;
634   
635   /* TODO: stylesheet table */
636   
637   /* FIXME: maybe emit something smarter for the generator? */
638   if (!ME_StreamOutPrint(editor, "{\\*\\generator Wine Riched20 2.0.????;}"))
639     return FALSE;
640
641   /* TODO: information group */
642
643   /* TODO: document formatting properties */
644
645   /* FIXME: We have only one document section */
646
647   /* TODO: section formatting properties */
648
649   if (!ME_StreamOutRTFParaProps(editor, ME_GetParagraph(p)))
650     return FALSE;
651
652   while(1)
653   {
654     switch(p->type)
655     {
656       case diParagraph:
657         if (!ME_StreamOutRTFParaProps(editor, p))
658           return FALSE;
659         break;
660       case diRun:
661         if (p == pEnd && !nEndLen)
662           break;
663         TRACE("flags %xh\n", p->member.run.nFlags);
664         /* TODO: emit embedded objects */
665         if (p->member.run.nFlags & MERF_GRAPHICS)
666           FIXME("embedded objects are not handled\n");
667         if (p->member.run.nFlags & MERF_ENDPARA) {
668           if (!ME_StreamOutPrint(editor, "\r\n\\par"))
669             return FALSE;
670           nChars--;
671         } else {
672           int nEnd;
673           
674           if (!ME_StreamOutPrint(editor, "{"))
675             return FALSE;
676           TRACE("style %p\n", p->member.run.style);
677           if (!ME_StreamOutRTFCharProps(editor, &p->member.run.style->fmt))
678             return FALSE;
679         
680           nEnd = (p == pEnd) ? nEndLen : ME_StrLen(p->member.run.strText);
681           if (!ME_StreamOutRTFText(editor, p->member.run.strText->szData + nOffset, nEnd - nOffset))
682             return FALSE;
683           nOffset = 0;
684           if (!ME_StreamOutPrint(editor, "}"))
685             return FALSE;
686         }
687         break;
688       default: /* we missed the last item */
689         assert(0);
690     }
691     if (p == pEnd)
692       break;
693     p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
694   }
695   if (!ME_StreamOutPrint(editor, "}"))
696     return FALSE;
697   return TRUE;
698 }
699
700
701 static BOOL
702 ME_StreamOutText(ME_TextEditor *editor, int nStart, int nChars, DWORD dwFormat)
703 {
704   /* FIXME: use ME_RunOfsFromCharOfs */
705   ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart);
706   int nLen;
707   UINT nCodePage = CP_ACP;
708   BYTE *buffer = NULL;
709   int nBufLen = 0;
710   BOOL success = TRUE;
711
712   if (!item)
713     return FALSE;
714    
715   if (dwFormat & SF_USECODEPAGE)
716     nCodePage = HIWORD(dwFormat);
717
718   /* TODO: Handle SF_TEXTIZED */
719   
720   while (success && nChars && item) {
721     nLen = ME_StrLen(item->member.run.strText) - nStart;
722     if (nLen > nChars)
723       nLen = nChars;
724
725     if (item->member.run.nFlags & MERF_ENDPARA) {
726       WCHAR szEOL[] = { '\r', '\n' };
727       
728       if (dwFormat & SF_UNICODE)
729         success = ME_StreamOutMove(editor, (BYTE *)szEOL, 4);
730       else
731         success = ME_StreamOutMove(editor, "\r\n", 2);
732     } else {
733       if (dwFormat & SF_UNICODE)
734         success = ME_StreamOutMove(editor, (BYTE *)(item->member.run.strText->szData + nStart),
735                                    sizeof(WCHAR) * nLen);
736       else {
737         int nSize;
738
739         nSize = WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
740                                     nLen, NULL, 0, NULL, NULL);
741         if (nSize > nBufLen) {
742           if (buffer)
743             FREE_OBJ(buffer);
744           buffer = ALLOC_N_OBJ(BYTE, nSize);
745           nBufLen = nSize;
746         }
747         WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
748                             nLen, buffer, nSize, NULL, NULL);
749         success = ME_StreamOutMove(editor, buffer, nSize - 1);
750       }
751     }
752     
753     nChars -= nLen;
754     nStart = 0;
755     item = ME_FindItemFwd(item, diRun);
756   }
757   
758   if (buffer)
759     FREE_OBJ(buffer);
760   return success;
761 }
762
763
764 LRESULT
765 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
766 {
767   int nStart, nTo;
768   
769   ME_StreamOutInit(editor, stream);
770
771   if (dwFormat & SFF_SELECTION)
772     ME_GetSelection(editor, &nStart, &nTo);
773   else {
774     nStart = 0;
775     nTo = -1;
776   }
777   if (nTo == -1)
778     nTo = ME_GetTextLength(editor);
779   TRACE("from %d to %d\n", nStart, nTo);
780   
781   if (dwFormat & SF_RTF || dwFormat & SF_RTFNOOBJS)
782     ME_StreamOutRTF(editor, nStart, nTo - nStart, dwFormat);
783   else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
784     ME_StreamOutText(editor, nStart, nTo - nStart, dwFormat);
785   
786   ME_StreamOutFlush(editor);
787   return ME_StreamOutFree(editor);
788 }