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