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