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