- WM_COPY (and WM_CUT) can now put both Unicode and RTF format (thanks
[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
23 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
24
25
26 static void
27 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
28 {
29   editor->pStream = ALLOC_OBJ(ME_OutStream);
30   editor->pStream->stream = stream;
31   editor->pStream->pos = 0;
32   editor->pStream->written = 0;
33   editor->pStream->nFontTblLen = 0;
34   editor->pStream->nColorTblLen = 1;
35 }
36
37
38 static BOOL
39 ME_StreamOutFlush(ME_TextEditor *editor)
40 {
41   LONG nStart = 0;
42   LONG nWritten = 0;
43   EDITSTREAM *stream = editor->pStream->stream;
44
45   do {
46     stream->dwError = stream->pfnCallback(stream->dwCookie, editor->pStream->buffer + nStart,
47                                           editor->pStream->pos - nStart, &nWritten);
48     if (nWritten == 0 || stream->dwError)
49       return FALSE;
50     editor->pStream->written += nWritten;
51     nStart += nWritten;
52   } while (nStart < editor->pStream->pos);
53   editor->pStream->pos = 0;
54   return TRUE;
55 }
56
57
58 static LONG
59 ME_StreamOutFree(ME_TextEditor *editor)
60 {
61   LONG written = editor->pStream->written;
62
63   FREE_OBJ(editor->pStream);
64   editor->pStream = NULL;
65   return written;
66 }
67
68
69 static BOOL
70 ME_StreamOutMove(ME_TextEditor *editor, BYTE *buffer, int len)
71 {
72   ME_OutStream *pStream = editor->pStream;
73   
74   while (len) {
75     int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
76     int fit = min(space, len);
77
78     TRACE("%u:%u:%.*s\n", pStream->pos, fit, fit, buffer);
79     memmove(pStream->buffer + pStream->pos, buffer, fit);
80     len -= fit;
81     buffer += fit;
82     pStream->pos += fit;
83     if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
84       if (!ME_StreamOutFlush(editor))
85         return FALSE;
86     }
87   }
88   return TRUE;
89 }
90
91
92 static BOOL
93 ME_StreamOutPrint(ME_TextEditor *editor, char *format, ...)
94 {
95   char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
96   int len;
97   va_list valist;
98
99   va_start(valist, format);
100   len = vsnprintf(string, sizeof(string), format, valist);
101   va_end(valist);
102   
103   return ME_StreamOutMove(editor, string, len);
104 }
105
106
107 static BOOL
108 ME_StreamOutRTFHeader(ME_TextEditor *editor, int dwFormat)
109 {
110   char *cCharSet = NULL;
111   UINT nCodePage;
112   LANGID language;
113   BOOL success;
114   
115   if (dwFormat & SF_USECODEPAGE) {
116     CPINFOEXW info;
117     
118     switch (HIWORD(dwFormat)) {
119       case CP_ACP:
120         cCharSet = "ansi";
121         nCodePage = GetACP();
122         break;
123       case CP_OEMCP:
124         nCodePage = GetOEMCP();
125         if (nCodePage == 437)
126           cCharSet = "pc";
127         else if (nCodePage == 850)
128           cCharSet = "pca";
129         else
130           cCharSet = "ansi";
131         break;
132       case CP_UTF8:
133         nCodePage = CP_UTF8;
134         break;
135       default:
136         if (HIWORD(dwFormat) == CP_MACCP) {
137           cCharSet = "mac";
138           nCodePage = 10000; /* MacRoman */
139         } else {
140           cCharSet = "ansi";
141           nCodePage = 1252; /* Latin-1 */
142         }
143         if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
144           nCodePage = info.CodePage;
145     }
146   } else {
147     cCharSet = "ansi";
148     nCodePage = GetACP();
149   }
150   if (nCodePage == CP_UTF8)
151     success = ME_StreamOutPrint(editor, "{\\urtf");
152   else
153     success = ME_StreamOutPrint(editor, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
154
155   if (!success)
156     return FALSE;
157
158   editor->pStream->nCodePage = nCodePage;
159   
160   /* FIXME: This should be a document property */
161   /* TODO: handle SFF_PLAINRTF */
162   language = GetUserDefaultLangID(); 
163   if (!ME_StreamOutPrint(editor, "\\deff0\\deflang%u\\deflangfe%u", language, language))
164     return FALSE;
165
166   /* FIXME: This should be a document property */
167   editor->pStream->nDefaultFont = 0;
168
169   return TRUE;
170 }
171
172
173 static BOOL
174 ME_StreamOutRTFFontAndColorTbl(ME_TextEditor *editor, ME_DisplayItem *pFirstRun, ME_DisplayItem *pLastRun)
175 {
176   ME_DisplayItem *item = pFirstRun;
177   ME_FontTableItem *table = editor->pStream->fonttbl;
178   int i;
179   
180   do {
181     CHARFORMAT2W *fmt = &item->member.run.style->fmt;
182     COLORREF crColor;
183
184     if (fmt->dwMask & CFM_FACE) {
185       WCHAR *face = fmt->szFaceName;
186       BYTE bCharSet = fmt->bCharSet;
187   
188       for (i = 0; i < editor->pStream->nFontTblLen; i++)
189         if (table[i].bCharSet == bCharSet
190             && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
191           break;
192       if (i == editor->pStream->nFontTblLen) {
193         table[i].bCharSet = bCharSet;
194         table[i].szFaceName = face;
195         editor->pStream->nFontTblLen++;
196       }
197     }
198     
199     if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) {
200       crColor = fmt->crTextColor;
201       for (i = 1; i < editor->pStream->nColorTblLen; i++)
202         if (editor->pStream->colortbl[i] == crColor)
203           break;
204       if (i == editor->pStream->nColorTblLen) {
205         editor->pStream->colortbl[i] = crColor;
206         editor->pStream->nColorTblLen++;
207       }
208     }
209     if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
210       crColor = fmt->crBackColor;
211       for (i = 1; i < editor->pStream->nColorTblLen; i++)
212         if (editor->pStream->colortbl[i] == crColor)
213           break;
214       if (i == editor->pStream->nColorTblLen) {
215         editor->pStream->colortbl[i] = crColor;
216         editor->pStream->nColorTblLen++;
217       }
218     }
219
220     if (item == pLastRun)
221       break;
222     item = ME_FindItemFwd(item, diRun);
223   } while (item);
224         
225   if (!ME_StreamOutPrint(editor, "{\\fonttbl"))
226     return FALSE;
227   
228   for (i = 0; i < editor->pStream->nFontTblLen; i++) {
229     char szFaceName[LF_FACESIZE];
230    
231     /* FIXME: Use ME_StreamOutText to emit the font name */
232     WideCharToMultiByte(editor->pStream->nCodePage, 0, table[i].szFaceName, -1,
233                         szFaceName, LF_FACESIZE, NULL, NULL);
234     if (table[i].bCharSet) {
235       if (!ME_StreamOutPrint(editor, "{\\f%u\\fcharset%u %s;}\r\n",
236                              i, table[i].bCharSet, szFaceName))
237         return FALSE;
238     } else {
239       if (!ME_StreamOutPrint(editor, "{\\f%u %s;}\r\n", i, szFaceName))
240         return FALSE;
241     }
242   }
243   if (!ME_StreamOutPrint(editor, "}"))
244     return FALSE;
245
246   /* Output colors table if not empty */
247   if (editor->pStream->nColorTblLen > 1) {
248     if (!ME_StreamOutPrint(editor, "{\\colortbl;"))
249       return FALSE;
250     for (i = 1; i < editor->pStream->nColorTblLen; i++) {
251       if (!ME_StreamOutPrint(editor, "\\red%u\\green%u\\blue%u;",
252                              editor->pStream->colortbl[i] & 0xFF,
253                              (editor->pStream->colortbl[i] >> 8) & 0xFF,
254                              (editor->pStream->colortbl[i] >> 16) & 0xFF))
255         return FALSE;
256     }
257     if (!ME_StreamOutPrint(editor, "}"))
258       return FALSE;
259   }
260
261   return TRUE;
262 }
263
264
265 static BOOL
266 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_DisplayItem *para)
267 {
268   char *keyword = NULL;
269
270   /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
271   if (!ME_StreamOutPrint(editor, "\\pard"))
272     return FALSE;
273    
274   switch (para->member.para.pFmt->wAlignment) {
275     case PFA_LEFT:
276       /* Default alignment: not emitted */
277       break;
278     case PFA_RIGHT:
279       keyword = "\\qr";
280       break;
281     case PFA_CENTER:
282       keyword = "\\qc";
283       break;
284     case PFA_JUSTIFY:
285       keyword = "\\qj";
286       break;
287   }
288   if (keyword && !ME_StreamOutPrint(editor, keyword))
289     return FALSE;
290
291   /* TODO: Other properties */
292   return TRUE;
293 }
294
295
296 static BOOL
297 ME_StreamOutRTFCharProps(ME_TextEditor *editor, CHARFORMAT2W *fmt)
298 {
299   char props[STREAMOUT_BUFFER_SIZE] = "";
300   int i;
301
302   if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
303     strcat(props, "\\caps");
304   if (fmt->dwMask & CFM_ANIMATION)
305     sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
306   if (fmt->dwMask & CFM_BACKCOLOR) {
307     if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
308       for (i = 1; i < editor->pStream->nColorTblLen; i++)
309         if (editor->pStream->colortbl[i] == fmt->crBackColor) {
310           sprintf(props + strlen(props), "\\cb%u", i);
311           break;
312         }
313     }
314   }
315   if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
316     strcat(props, "\\b");
317   if (fmt->dwMask & CFM_COLOR) {
318     if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
319       for (i = 1; i < editor->pStream->nColorTblLen; i++)
320         if (editor->pStream->colortbl[i] == fmt->crTextColor) {
321           sprintf(props + strlen(props), "\\cf%u", i);
322           break;
323         }
324     }
325   }
326   /* TODO: CFM_DISABLED */
327   if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
328     strcat(props, "\\embo");
329   if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
330     strcat(props, "\\v");
331   if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
332     strcat(props, "\\impr");
333   if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
334     strcat(props, "\\i");
335   if (fmt->dwMask & CFM_KERNING)
336     sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
337   if (fmt->dwMask & CFM_LCID) {
338     /* TODO: handle SFF_PLAINRTF */
339     if (LOWORD(fmt->lcid) == 1024)
340       strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
341     else
342       sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
343   }
344   /* CFM_LINK is not streamed out by M$ */
345   if (fmt->dwMask & CFM_OFFSET) {
346     if (fmt->yOffset >= 0)
347       sprintf(props + strlen(props), "\\up%ld", fmt->yOffset);
348     else
349       sprintf(props + strlen(props), "\\dn%ld", -fmt->yOffset);
350   }
351   if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
352     strcat(props, "\\outl");
353   if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
354     strcat(props, "\\protect");
355   /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
356   if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
357     strcat(props, "\\shad");
358   if (fmt->dwMask & CFM_SIZE && fmt->yHeight / 10 != 24)
359     sprintf(props + strlen(props), "\\fs%ld", fmt->yHeight / 10);
360   if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
361     strcat(props, "\\scaps");
362   if (fmt->dwMask & CFM_SPACING)
363     sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
364   if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
365     strcat(props, "\\strike");
366   if (fmt->dwMask & CFM_STYLE) {
367     sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
368     /* TODO: emit style contents here */
369   }
370   if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
371     if (fmt->dwEffects & CFE_SUBSCRIPT)
372       strcat(props, "\\sub");
373     else if (fmt->dwEffects & CFE_SUPERSCRIPT)
374       strcat(props, "\\super");
375   }
376   if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
377     if (fmt->dwMask & CFM_UNDERLINETYPE)
378       switch (fmt->bUnderlineType) {
379         case CFU_CF1UNDERLINE:
380         case CFU_UNDERLINE:
381           strcat(props, "\\ul");
382           break;
383         case CFU_UNDERLINEDOTTED:
384           strcat(props, "\\uld");
385           break;
386         case CFU_UNDERLINEDOUBLE:
387           strcat(props, "\\uldb");
388           break;
389         case CFU_UNDERLINEWORD:
390           strcat(props, "\\ulw");
391           break;
392         case CFU_UNDERLINENONE:
393         default:
394           strcat(props, "\\ul0");
395           break;
396       }
397     else if (fmt->dwEffects & CFE_UNDERLINE)
398       strcat(props, "\\ul");
399   }
400   /* FIXME: How to emit CFM_WEIGHT? */
401   
402   if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
403     WCHAR *szFaceName;
404     
405     if (fmt->dwMask & CFM_FACE)
406       szFaceName = fmt->szFaceName;
407     else
408       szFaceName = editor->pStream->fonttbl[0].szFaceName;
409     for (i = 0; i < editor->pStream->nFontTblLen; i++) {
410       if (szFaceName == editor->pStream->fonttbl[i].szFaceName
411           || !lstrcmpW(szFaceName, editor->pStream->fonttbl[i].szFaceName))
412         if (!(fmt->dwMask & CFM_CHARSET)
413             || fmt->bCharSet == editor->pStream->fonttbl[i].bCharSet)
414           break;
415     }
416     if (i < editor->pStream->nFontTblLen && i != editor->pStream->nDefaultFont)
417       sprintf(props + strlen(props), "\\f%u", i);
418   }
419   if (*props)
420     strcat(props, " ");
421   if (!ME_StreamOutPrint(editor, props))
422     return FALSE;
423   return TRUE;
424 }
425
426
427 static BOOL
428 ME_StreamOutRTFText(ME_TextEditor *editor, WCHAR *text, LONG nChars)
429 {
430   char buffer[STREAMOUT_BUFFER_SIZE];
431   int pos = 0;
432   int fit, i;
433  
434   while (nChars) {
435     if (editor->pStream->nCodePage == CP_UTF8) {
436       /* 6 is the maximum character length in UTF-8 */
437       fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
438       WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer, STREAMOUT_BUFFER_SIZE,
439                           NULL, NULL);
440       nChars -= fit;
441       text += fit;
442       for (i = 0; buffer[i]; i++)
443         if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
444           if (!ME_StreamOutPrint(editor, "%.*s\\", i - pos, buffer + pos))
445             return FALSE;
446           pos = i;
447         }
448       if (!pos)
449         if (!ME_StreamOutPrint(editor, "%s", buffer + pos))
450           return FALSE;
451       pos = 0;
452     } else if (*text < 128) {
453       if (*text == '{' || *text == '}' || *text == '\\')
454         buffer[pos++] = '\\';
455       buffer[pos++] = (char)(*text++);
456       nChars--;
457     } else {
458       BOOL unknown;
459       BYTE letter[2];
460       
461       WideCharToMultiByte(editor->pStream->nCodePage, 0, text, 1, letter, 2,
462                           NULL, &unknown);
463       if (unknown)
464         pos += sprintf(buffer + pos, "\\u%d?", (int)*text);
465       else if (*letter < 128) {
466         if (*letter == '{' || *letter == '}' || *letter == '\\')
467           buffer[pos++] = '\\';
468         buffer[pos++] = *letter;
469       } else
470         pos += sprintf(buffer + pos, "\\'%02x", *letter);
471       text++;
472       nChars--;
473     }
474     if (pos >= STREAMOUT_BUFFER_SIZE - 10) {
475       if (!ME_StreamOutMove(editor, buffer, pos))
476         return FALSE;
477       pos = 0;
478     }
479   }
480   return ME_StreamOutMove(editor, buffer, pos);
481 }
482
483
484 static BOOL
485 ME_StreamOutRTF(ME_TextEditor *editor, int nStart, int nChars, int dwFormat)
486 {
487   ME_DisplayItem *p, *pEnd;
488   int nOffset, nEndLen;
489   ME_RunOfsFromCharOfs(editor, nStart, &p, &nOffset);
490   ME_RunOfsFromCharOfs(editor, nStart+nChars, &pEnd, &nEndLen);
491   
492   if (!ME_StreamOutRTFHeader(editor, dwFormat))
493     return FALSE;
494
495   if (!ME_StreamOutRTFFontAndColorTbl(editor, p, pEnd))
496     return FALSE;
497   
498   /* TODO: stylesheet table */
499   
500   /* FIXME: maybe emit something smarter for the generator? */
501   if (!ME_StreamOutPrint(editor, "{\\*\\generator Wine Riched20 2.0.????;}"))
502     return FALSE;
503
504   /* TODO: information group */
505
506   /* TODO: document formatting properties */
507
508   /* FIXME: We have only one document section */
509
510   /* TODO: section formatting properties */
511
512   if (!ME_StreamOutRTFParaProps(editor, ME_GetParagraph(p)))
513     return FALSE;
514
515   while(1)
516   {
517     switch(p->type)
518     {
519       case diParagraph:
520         if (!ME_StreamOutRTFParaProps(editor, p))
521           return FALSE;
522         break;
523       case diRun:
524         if (p == pEnd && !nEndLen)
525           break;
526         TRACE("flags %xh\n", p->member.run.nFlags);
527         /* TODO: emit embedded objects */
528         if (p->member.run.nFlags & MERF_GRAPHICS)
529           FIXME("embedded objects are not handled\n");
530         if (p->member.run.nFlags & MERF_ENDPARA) {
531           if (!ME_StreamOutPrint(editor, "\r\n\\par"))
532             return FALSE;
533           nChars--;
534         } else {
535           int nEnd;
536           
537           if (!ME_StreamOutPrint(editor, "{"))
538             return FALSE;
539           TRACE("style %p\n", p->member.run.style);
540           if (!ME_StreamOutRTFCharProps(editor, &p->member.run.style->fmt))
541             return FALSE;
542         
543           nEnd = (p == pEnd) ? nEndLen : ME_StrLen(p->member.run.strText);
544           if (!ME_StreamOutRTFText(editor, p->member.run.strText->szData + nOffset, nEnd - nOffset))
545             return FALSE;
546           nOffset = 0;
547           if (!ME_StreamOutPrint(editor, "}"))
548             return FALSE;
549         }
550         break;
551       default: /* we missed the last item */
552         assert(0);
553     }
554     if (p == pEnd)
555       break;
556     p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
557   }
558   if (!ME_StreamOutPrint(editor, "}"))
559     return FALSE;
560   return TRUE;
561 }
562
563
564 static BOOL
565 ME_StreamOutText(ME_TextEditor *editor, int nStart, int nChars, DWORD dwFormat)
566 {
567   /* FIXME: use ME_RunOfsFromCharOfs */
568   ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart);
569   int nLen;
570   UINT nCodePage = CP_ACP;
571   BYTE *buffer = NULL;
572   int nBufLen = 0;
573   BOOL success = TRUE;
574
575   if (!item)
576     return FALSE;
577    
578   if (dwFormat & SF_USECODEPAGE)
579     nCodePage = HIWORD(dwFormat);
580
581   /* TODO: Handle SF_TEXTIZED */
582   
583   while (success && nChars && item) {
584     nLen = ME_StrLen(item->member.run.strText) - nStart;
585     if (nLen > nChars)
586       nLen = nChars;
587
588     if (item->member.run.nFlags & MERF_ENDPARA) {
589       WCHAR szEOL[] = { '\r', '\n' };
590       
591       if (dwFormat & SF_UNICODE)
592         success = ME_StreamOutMove(editor, (BYTE *)szEOL, 4);
593       else
594         success = ME_StreamOutMove(editor, "\r\n", 2);
595     } else {
596       if (dwFormat & SF_UNICODE)
597         success = ME_StreamOutMove(editor, (BYTE *)(item->member.run.strText->szData + nStart),
598                                    sizeof(WCHAR) * nLen);
599       else {
600         int nSize;
601
602         nSize = WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
603                                     nLen, NULL, 0, NULL, NULL);
604         if (nSize > nBufLen) {
605           if (buffer)
606             FREE_OBJ(buffer);
607           buffer = ALLOC_N_OBJ(BYTE, nSize);
608           nBufLen = nSize;
609         }
610         WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
611                             nLen, buffer, nSize, NULL, NULL);
612         success = ME_StreamOutMove(editor, buffer, nSize - 1);
613       }
614     }
615     
616     nChars -= nLen;
617     nStart = 0;
618     item = ME_FindItemFwd(item, diRun);
619   }
620   
621   if (buffer)
622     FREE_OBJ(buffer);
623   return success;
624 }
625
626
627 LRESULT
628 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
629 {
630   int nStart, nTo;
631   
632   ME_StreamOutInit(editor, stream);
633
634   if (dwFormat & SFF_SELECTION)
635     ME_GetSelection(editor, &nStart, &nTo);
636   else {
637     nStart = 0;
638     nTo = -1;
639   }
640   if (nTo == -1)
641     nTo = ME_GetTextLength(editor);
642   TRACE("from %d to %d\n", nStart, nTo);
643   
644   if (dwFormat & SF_RTF || dwFormat & SF_RTFNOOBJS)
645     ME_StreamOutRTF(editor, nStart, nTo - nStart, dwFormat);
646   else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
647     ME_StreamOutText(editor, nStart, nTo - nStart, dwFormat);
648   
649   ME_StreamOutFlush(editor);
650   return ME_StreamOutFree(editor);
651 }