7d2b4b7889bbbdf6e7a627cb85c0a1f7de4133ba
[reactos.git] / reactos / dll / win32 / 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 "editor.h"
22 #include "rtf.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
25
26 #define STREAMOUT_BUFFER_SIZE 4096
27 #define STREAMOUT_FONTTBL_SIZE 8192
28 #define STREAMOUT_COLORTBL_SIZE 1024
29
30 typedef struct tagME_OutStream
31 {
32 EDITSTREAM *stream;
33 char buffer[STREAMOUT_BUFFER_SIZE];
34 UINT pos, written;
35 UINT nCodePage;
36 UINT nFontTblLen;
37 ME_FontTableItem fonttbl[STREAMOUT_FONTTBL_SIZE];
38 UINT nColorTblLen;
39 COLORREF colortbl[STREAMOUT_COLORTBL_SIZE];
40 UINT nDefaultFont;
41 UINT nDefaultCodePage;
42 /* nNestingLevel = 0 means we aren't in a cell, 1 means we are in a cell,
43 * an greater numbers mean we are in a cell nested within a cell. */
44 UINT nNestingLevel;
45 CHARFORMAT2W cur_fmt; /* current character format */
46 } ME_OutStream;
47
48 static BOOL
49 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
50
51
52 static ME_OutStream*
53 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
54 {
55 ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream);
56 pStream->stream = stream;
57 pStream->stream->dwError = 0;
58 pStream->pos = 0;
59 pStream->written = 0;
60 pStream->nFontTblLen = 0;
61 pStream->nColorTblLen = 1;
62 pStream->nNestingLevel = 0;
63 memset(&pStream->cur_fmt, 0, sizeof(pStream->cur_fmt));
64 pStream->cur_fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
65 pStream->cur_fmt.bUnderlineType = CFU_UNDERLINE;
66 return pStream;
67 }
68
69
70 static BOOL
71 ME_StreamOutFlush(ME_OutStream *pStream)
72 {
73 LONG nWritten = 0;
74 EDITSTREAM *stream = pStream->stream;
75
76 if (pStream->pos) {
77 TRACE("sending %u bytes\n", pStream->pos);
78 nWritten = pStream->pos;
79 stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer,
80 pStream->pos, &nWritten);
81 TRACE("error=%u written=%u\n", stream->dwError, nWritten);
82 if (nWritten == 0 || stream->dwError)
83 return FALSE;
84 /* Don't resend partial chunks if nWritten < pStream->pos */
85 }
86 if (nWritten == pStream->pos)
87 pStream->written += nWritten;
88 pStream->pos = 0;
89 return TRUE;
90 }
91
92
93 static LONG
94 ME_StreamOutFree(ME_OutStream *pStream)
95 {
96 LONG written = pStream->written;
97 TRACE("total length = %u\n", written);
98
99 FREE_OBJ(pStream);
100 return written;
101 }
102
103
104 static BOOL
105 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
106 {
107 while (len) {
108 int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
109 int fit = min(space, len);
110
111 TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
112 memmove(pStream->buffer + pStream->pos, buffer, fit);
113 len -= fit;
114 buffer += fit;
115 pStream->pos += fit;
116 if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
117 if (!ME_StreamOutFlush(pStream))
118 return FALSE;
119 }
120 }
121 return TRUE;
122 }
123
124
125 static BOOL
126 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
127 {
128 char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
129 int len;
130 va_list valist;
131
132 va_start(valist, format);
133 len = vsnprintf(string, sizeof(string), format, valist);
134 va_end(valist);
135
136 return ME_StreamOutMove(pStream, string, len);
137 }
138
139 #define HEX_BYTES_PER_LINE 40
140
141 static BOOL
142 ME_StreamOutHexData(ME_OutStream *stream, const BYTE *data, UINT len)
143 {
144
145 char line[HEX_BYTES_PER_LINE * 2 + 1];
146 UINT size, i;
147 static const char hex[] = "0123456789abcdef";
148
149 while (len)
150 {
151 size = min( len, HEX_BYTES_PER_LINE );
152 for (i = 0; i < size; i++)
153 {
154 line[i * 2] = hex[(*data >> 4) & 0xf];
155 line[i * 2 + 1] = hex[*data & 0xf];
156 data++;
157 }
158 line[size * 2] = '\n';
159 if (!ME_StreamOutMove( stream, line, size * 2 + 1 ))
160 return FALSE;
161 len -= size;
162 }
163 return TRUE;
164 }
165
166 static BOOL
167 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
168 {
169 const char *cCharSet = NULL;
170 UINT nCodePage;
171 LANGID language;
172 BOOL success;
173
174 if (dwFormat & SF_USECODEPAGE) {
175 CPINFOEXW info;
176
177 switch (HIWORD(dwFormat)) {
178 case CP_ACP:
179 cCharSet = "ansi";
180 nCodePage = GetACP();
181 break;
182 case CP_OEMCP:
183 nCodePage = GetOEMCP();
184 if (nCodePage == 437)
185 cCharSet = "pc";
186 else if (nCodePage == 850)
187 cCharSet = "pca";
188 else
189 cCharSet = "ansi";
190 break;
191 case CP_UTF8:
192 nCodePage = CP_UTF8;
193 break;
194 default:
195 if (HIWORD(dwFormat) == CP_MACCP) {
196 cCharSet = "mac";
197 nCodePage = 10000; /* MacRoman */
198 } else {
199 cCharSet = "ansi";
200 nCodePage = 1252; /* Latin-1 */
201 }
202 if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
203 nCodePage = info.CodePage;
204 }
205 } else {
206 cCharSet = "ansi";
207 /* TODO: If the original document contained an \ansicpg value, retain it.
208 * Otherwise, M$ richedit emits a codepage number determined from the
209 * charset of the default font here. Anyway, this value is not used by
210 * the reader... */
211 nCodePage = GetACP();
212 }
213 if (nCodePage == CP_UTF8)
214 success = ME_StreamOutPrint(pStream, "{\\urtf");
215 else
216 success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
217
218 if (!success)
219 return FALSE;
220
221 pStream->nDefaultCodePage = nCodePage;
222
223 /* FIXME: This should be a document property */
224 /* TODO: handle SFF_PLAINRTF */
225 language = GetUserDefaultLangID();
226 if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
227 return FALSE;
228
229 /* FIXME: This should be a document property */
230 pStream->nDefaultFont = 0;
231
232 return TRUE;
233 }
234
235 static void add_font_to_fonttbl( ME_OutStream *stream, ME_Style *style )
236 {
237 ME_FontTableItem *table = stream->fonttbl;
238 CHARFORMAT2W *fmt = &style->fmt;
239 WCHAR *face = fmt->szFaceName;
240 BYTE charset = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
241 int i;
242
243 if (fmt->dwMask & CFM_FACE)
244 {
245 for (i = 0; i < stream->nFontTblLen; i++)
246 if (table[i].bCharSet == charset
247 && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
248 break;
249
250 if (i == stream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE)
251 {
252 table[i].bCharSet = charset;
253 table[i].szFaceName = face;
254 stream->nFontTblLen++;
255 }
256 }
257 }
258
259 static BOOL find_font_in_fonttbl( ME_OutStream *stream, CHARFORMAT2W *fmt, unsigned int *idx )
260 {
261 WCHAR *facename;
262 int i;
263
264 *idx = 0;
265 if (fmt->dwMask & CFM_FACE)
266 facename = fmt->szFaceName;
267 else
268 facename = stream->fonttbl[0].szFaceName;
269 for (i = 0; i < stream->nFontTblLen; i++)
270 {
271 if (facename == stream->fonttbl[i].szFaceName
272 || !lstrcmpW(facename, stream->fonttbl[i].szFaceName))
273 if (!(fmt->dwMask & CFM_CHARSET)
274 || fmt->bCharSet == stream->fonttbl[i].bCharSet)
275 {
276 *idx = i;
277 break;
278 }
279 }
280
281 return i < stream->nFontTblLen;
282 }
283
284 static void add_color_to_colortbl( ME_OutStream *stream, COLORREF color )
285 {
286 int i;
287
288 for (i = 1; i < stream->nColorTblLen; i++)
289 if (stream->colortbl[i] == color)
290 break;
291
292 if (i == stream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE)
293 {
294 stream->colortbl[i] = color;
295 stream->nColorTblLen++;
296 }
297 }
298
299 static BOOL find_color_in_colortbl( ME_OutStream *stream, COLORREF color, unsigned int *idx )
300 {
301 int i;
302
303 *idx = 0;
304 for (i = 1; i < stream->nColorTblLen; i++)
305 {
306 if (stream->colortbl[i] == color)
307 {
308 *idx = i;
309 break;
310 }
311 }
312
313 return i < stream->nFontTblLen;
314 }
315
316 static BOOL
317 ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun,
318 ME_DisplayItem *pLastRun)
319 {
320 ME_DisplayItem *item = pFirstRun;
321 ME_FontTableItem *table = pStream->fonttbl;
322 unsigned int i;
323 ME_DisplayItem *pCell = NULL;
324 ME_Paragraph *prev_para = NULL;
325
326 do {
327 CHARFORMAT2W *fmt = &item->member.run.style->fmt;
328
329 add_font_to_fonttbl( pStream, item->member.run.style );
330
331 if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR))
332 add_color_to_colortbl( pStream, fmt->crTextColor );
333 if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR))
334 add_color_to_colortbl( pStream, fmt->crBackColor );
335
336 if (item->member.run.para != prev_para)
337 {
338 /* check for any para numbering text */
339 if (item->member.run.para->fmt.wNumbering)
340 add_font_to_fonttbl( pStream, item->member.run.para->para_num.style );
341
342 if ((pCell = item->member.para.pCell))
343 {
344 ME_Border* borders[4] = { &pCell->member.cell.border.top,
345 &pCell->member.cell.border.left,
346 &pCell->member.cell.border.bottom,
347 &pCell->member.cell.border.right };
348 for (i = 0; i < 4; i++)
349 if (borders[i]->width > 0)
350 add_color_to_colortbl( pStream, borders[i]->colorRef );
351 }
352
353 prev_para = item->member.run.para;
354 }
355
356 if (item == pLastRun)
357 break;
358 item = ME_FindItemFwd(item, diRun);
359 } while (item);
360
361 if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
362 return FALSE;
363
364 for (i = 0; i < pStream->nFontTblLen; i++) {
365 if (table[i].bCharSet != DEFAULT_CHARSET) {
366 if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
367 return FALSE;
368 } else {
369 if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
370 return FALSE;
371 }
372 if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
373 return FALSE;
374 if (!ME_StreamOutPrint(pStream, ";}"))
375 return FALSE;
376 }
377 if (!ME_StreamOutPrint(pStream, "}\r\n"))
378 return FALSE;
379
380 /* It seems like Open Office ignores \deff0 tag at RTF-header.
381 As result it can't correctly parse text before first \fN tag,
382 so we can put \f0 immediately after font table. This forces
383 parser to use the same font, that \deff0 specifies.
384 It makes OOffice happy */
385 if (!ME_StreamOutPrint(pStream, "\\f0"))
386 return FALSE;
387
388 /* Output the color table */
389 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
390 for (i = 1; i < pStream->nColorTblLen; i++)
391 {
392 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
393 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
394 return FALSE;
395 }
396 if (!ME_StreamOutPrint(pStream, "}")) return FALSE;
397
398 return TRUE;
399 }
400
401 static BOOL
402 ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
403 ME_DisplayItem *para)
404 {
405 ME_DisplayItem *cell;
406 char props[STREAMOUT_BUFFER_SIZE] = "";
407 int i;
408 const char sideChar[4] = {'t','l','b','r'};
409
410 if (!ME_StreamOutPrint(pStream, "\\trowd"))
411 return FALSE;
412 if (!editor->bEmulateVersion10) { /* v4.1 */
413 PARAFORMAT2 *pFmt = &ME_GetTableRowEnd(para)->member.para.fmt;
414 para = ME_GetTableRowStart(para);
415 cell = para->member.para.next_para->member.para.pCell;
416 assert(cell);
417 if (pFmt->dxOffset)
418 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
419 if (pFmt->dxStartIndent)
420 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
421 do {
422 ME_Border* borders[4] = { &cell->member.cell.border.top,
423 &cell->member.cell.border.left,
424 &cell->member.cell.border.bottom,
425 &cell->member.cell.border.right };
426 for (i = 0; i < 4; i++)
427 {
428 if (borders[i]->width)
429 {
430 unsigned int idx;
431 COLORREF crColor = borders[i]->colorRef;
432 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
433 sprintf(props + strlen(props), "\\brdrs");
434 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
435 if (find_color_in_colortbl( pStream, crColor, &idx ))
436 sprintf(props + strlen(props), "\\brdrcf%u", idx);
437 }
438 }
439 sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
440 cell = cell->member.cell.next_cell;
441 } while (cell->member.cell.next_cell);
442 } else { /* v1.0 - 3.0 */
443 const ME_Border* borders[4] = { &para->member.para.border.top,
444 &para->member.para.border.left,
445 &para->member.para.border.bottom,
446 &para->member.para.border.right };
447 PARAFORMAT2 *pFmt = &para->member.para.fmt;
448
449 assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
450 if (pFmt->dxOffset)
451 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
452 if (pFmt->dxStartIndent)
453 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
454 for (i = 0; i < 4; i++)
455 {
456 if (borders[i]->width)
457 {
458 unsigned int idx;
459 COLORREF crColor = borders[i]->colorRef;
460 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
461 sprintf(props + strlen(props), "\\brdrs");
462 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
463 if (find_color_in_colortbl( pStream, crColor, &idx ))
464 sprintf(props + strlen(props), "\\brdrcf%u", idx);
465 }
466 }
467 for (i = 0; i < pFmt->cTabCount; i++)
468 {
469 sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
470 }
471 }
472 if (!ME_StreamOutPrint(pStream, props))
473 return FALSE;
474 props[0] = '\0';
475 return TRUE;
476 }
477
478 static BOOL stream_out_para_num( ME_OutStream *stream, ME_Paragraph *para, BOOL pn_dest )
479 {
480 static const char fmt_label[] = "{\\*\\pn\\pnlvlbody\\pnf%u\\pnindent%d\\pnstart%d%s%s}";
481 static const char fmt_bullet[] = "{\\*\\pn\\pnlvlblt\\pnf%u\\pnindent%d{\\pntxtb\\'b7}}";
482 static const char dec[] = "\\pndec";
483 static const char lcltr[] = "\\pnlcltr";
484 static const char ucltr[] = "\\pnucltr";
485 static const char lcrm[] = "\\pnlcrm";
486 static const char ucrm[] = "\\pnucrm";
487 static const char period[] = "{\\pntxta.}";
488 static const char paren[] = "{\\pntxta)}";
489 static const char parens[] = "{\\pntxtb(}{\\pntxta)}";
490 const char *type, *style = "";
491 unsigned int idx;
492
493 find_font_in_fonttbl( stream, &para->para_num.style->fmt, &idx );
494
495 if (!ME_StreamOutPrint( stream, "{\\pntext\\f%u ", idx )) return FALSE;
496 if (!ME_StreamOutRTFText( stream, para->para_num.text->szData, para->para_num.text->nLen ))
497 return FALSE;
498 if (!ME_StreamOutPrint( stream, "\\tab}" )) return FALSE;
499
500 if (!pn_dest) return TRUE;
501
502 if (para->fmt.wNumbering == PFN_BULLET)
503 {
504 if (!ME_StreamOutPrint( stream, fmt_bullet, idx, para->fmt.wNumberingTab ))
505 return FALSE;
506 }
507 else
508 {
509 switch (para->fmt.wNumbering)
510 {
511 case PFN_ARABIC:
512 default:
513 type = dec;
514 break;
515 case PFN_LCLETTER:
516 type = lcltr;
517 break;
518 case PFN_UCLETTER:
519 type = ucltr;
520 break;
521 case PFN_LCROMAN:
522 type = lcrm;
523 break;
524 case PFN_UCROMAN:
525 type = ucrm;
526 break;
527 }
528 switch (para->fmt.wNumberingStyle & 0xf00)
529 {
530 case PFNS_PERIOD:
531 style = period;
532 break;
533 case PFNS_PAREN:
534 style = paren;
535 break;
536 case PFNS_PARENS:
537 style = parens;
538 break;
539 }
540
541 if (!ME_StreamOutPrint( stream, fmt_label, idx, para->fmt.wNumberingTab,
542 para->fmt.wNumberingStart, type, style ))
543 return FALSE;
544 }
545 return TRUE;
546 }
547
548 static BOOL
549 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
550 ME_DisplayItem *para)
551 {
552 PARAFORMAT2 *fmt = &para->member.para.fmt;
553 char props[STREAMOUT_BUFFER_SIZE] = "";
554 int i;
555 ME_Paragraph *prev_para = NULL;
556
557 if (para->member.para.prev_para->type == diParagraph)
558 prev_para = &para->member.para.prev_para->member.para;
559
560 if (!editor->bEmulateVersion10) { /* v4.1 */
561 if (para->member.para.nFlags & MEPF_ROWSTART) {
562 pStream->nNestingLevel++;
563 if (pStream->nNestingLevel == 1) {
564 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
565 return FALSE;
566 }
567 return TRUE;
568 } else if (para->member.para.nFlags & MEPF_ROWEND) {
569 pStream->nNestingLevel--;
570 if (pStream->nNestingLevel >= 1) {
571 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
572 return FALSE;
573 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
574 return FALSE;
575 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
576 return FALSE;
577 } else {
578 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
579 return FALSE;
580 }
581 return TRUE;
582 }
583 } else { /* v1.0 - 3.0 */
584 if (para->member.para.fmt.dwMask & PFM_TABLE &&
585 para->member.para.fmt.wEffects & PFE_TABLE)
586 {
587 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
588 return FALSE;
589 }
590 }
591
592 if (prev_para && !memcmp( fmt, &prev_para->fmt, sizeof(*fmt) ))
593 {
594 if (fmt->wNumbering)
595 return stream_out_para_num( pStream, &para->member.para, FALSE );
596 return TRUE;
597 }
598
599 if (!ME_StreamOutPrint(pStream, "\\pard"))
600 return FALSE;
601
602 if (fmt->wNumbering)
603 if (!stream_out_para_num( pStream, &para->member.para, TRUE )) return FALSE;
604
605 if (!editor->bEmulateVersion10) { /* v4.1 */
606 if (pStream->nNestingLevel > 0)
607 strcat(props, "\\intbl");
608 if (pStream->nNestingLevel > 1)
609 sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
610 } else { /* v1.0 - 3.0 */
611 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
612 strcat(props, "\\intbl");
613 }
614
615 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
616 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
617 * set very different from the documentation.
618 * (Tested with RichEdit 5.50.25.0601) */
619
620 if (fmt->dwMask & PFM_ALIGNMENT) {
621 switch (fmt->wAlignment) {
622 case PFA_LEFT:
623 /* Default alignment: not emitted */
624 break;
625 case PFA_RIGHT:
626 strcat(props, "\\qr");
627 break;
628 case PFA_CENTER:
629 strcat(props, "\\qc");
630 break;
631 case PFA_JUSTIFY:
632 strcat(props, "\\qj");
633 break;
634 }
635 }
636
637 if (fmt->dwMask & PFM_LINESPACING) {
638 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
639 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
640 switch (fmt->bLineSpacingRule) {
641 case 0: /* Single spacing */
642 strcat(props, "\\sl-240\\slmult1");
643 break;
644 case 1: /* 1.5 spacing */
645 strcat(props, "\\sl-360\\slmult1");
646 break;
647 case 2: /* Double spacing */
648 strcat(props, "\\sl-480\\slmult1");
649 break;
650 case 3:
651 sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
652 break;
653 case 4:
654 sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
655 break;
656 case 5:
657 sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
658 break;
659 }
660 }
661
662 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
663 strcat(props, "\\hyph0");
664 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
665 strcat(props, "\\keep");
666 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
667 strcat(props, "\\keepn");
668 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
669 strcat(props, "\\noline");
670 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
671 strcat(props, "\\nowidctlpar");
672 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
673 strcat(props, "\\pagebb");
674 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
675 strcat(props, "\\rtlpar");
676 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
677 strcat(props, "\\sbys");
678
679 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
680 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
681 {
682 if (fmt->dxOffset)
683 sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
684 if (fmt->dxStartIndent)
685 sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
686 if (fmt->dxRightIndent)
687 sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
688 if (fmt->dwMask & PFM_TABSTOPS) {
689 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
690
691 for (i = 0; i < fmt->cTabCount; i++) {
692 switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
693 case 1:
694 strcat(props, "\\tqc");
695 break;
696 case 2:
697 strcat(props, "\\tqr");
698 break;
699 case 3:
700 strcat(props, "\\tqdec");
701 break;
702 case 4:
703 /* Word bar tab (vertical bar). Handled below */
704 break;
705 }
706 if (fmt->rgxTabs[i] >> 28 <= 5)
707 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
708 sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
709 }
710 }
711 }
712 if (fmt->dySpaceAfter)
713 sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
714 if (fmt->dySpaceBefore)
715 sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
716 if (fmt->sStyle != -1)
717 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
718
719 if (fmt->dwMask & PFM_SHADING) {
720 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
721 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
722 "\\bghoriz", "\\bgvert", "\\bgfdiag",
723 "\\bgbdiag", "\\bgcross", "\\bgdcross",
724 "", "", "" };
725 if (fmt->wShadingWeight)
726 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
727 if (fmt->wShadingStyle & 0xF)
728 strcat(props, style[fmt->wShadingStyle & 0xF]);
729 sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
730 (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
731 }
732 if (*props)
733 strcat(props, " ");
734
735 if (*props && !ME_StreamOutPrint(pStream, props))
736 return FALSE;
737
738 return TRUE;
739 }
740
741
742 static BOOL
743 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
744 {
745 char props[STREAMOUT_BUFFER_SIZE] = "";
746 unsigned int i;
747 CHARFORMAT2W *old_fmt = &pStream->cur_fmt;
748 static const struct
749 {
750 DWORD effect;
751 const char *on, *off;
752 } effects[] =
753 {
754 { CFE_ALLCAPS, "\\caps", "\\caps0" },
755 { CFE_BOLD, "\\b", "\\b0" },
756 { CFE_DISABLED, "\\disabled", "\\disabled0" },
757 { CFE_EMBOSS, "\\embo", "\\embo0" },
758 { CFE_HIDDEN, "\\v", "\\v0" },
759 { CFE_IMPRINT, "\\impr", "\\impr0" },
760 { CFE_ITALIC, "\\i", "\\i0" },
761 { CFE_OUTLINE, "\\outl", "\\outl0" },
762 { CFE_PROTECTED, "\\protect", "\\protect0" },
763 { CFE_SHADOW, "\\shad", "\\shad0" },
764 { CFE_SMALLCAPS, "\\scaps", "\\scaps0" },
765 { CFE_STRIKEOUT, "\\strike", "\\strike0" },
766 };
767
768 for (i = 0; i < sizeof(effects) / sizeof(effects[0]); i++)
769 {
770 if ((old_fmt->dwEffects ^ fmt->dwEffects) & effects[i].effect)
771 strcat( props, fmt->dwEffects & effects[i].effect ? effects[i].on : effects[i].off );
772 }
773
774 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOBACKCOLOR ||
775 old_fmt->crBackColor != fmt->crBackColor)
776 {
777 if (fmt->dwEffects & CFE_AUTOBACKCOLOR) i = 0;
778 else find_color_in_colortbl( pStream, fmt->crBackColor, &i );
779 sprintf(props + strlen(props), "\\cb%u", i);
780 }
781 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOCOLOR ||
782 old_fmt->crTextColor != fmt->crTextColor)
783 {
784 if (fmt->dwEffects & CFE_AUTOCOLOR) i = 0;
785 else find_color_in_colortbl( pStream, fmt->crTextColor, &i );
786 sprintf(props + strlen(props), "\\cf%u", i);
787 }
788
789 if (old_fmt->bAnimation != fmt->bAnimation)
790 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
791 if (old_fmt->wKerning != fmt->wKerning)
792 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
793
794 if (old_fmt->lcid != fmt->lcid)
795 {
796 /* TODO: handle SFF_PLAINRTF */
797 if (LOWORD(fmt->lcid) == 1024)
798 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
799 else
800 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
801 }
802
803 if (old_fmt->yOffset != fmt->yOffset)
804 {
805 if (fmt->yOffset >= 0)
806 sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
807 else
808 sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
809 }
810 if (old_fmt->yHeight != fmt->yHeight)
811 sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
812 if (old_fmt->sSpacing != fmt->sSpacing)
813 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
814 if ((old_fmt->dwEffects ^ fmt->dwEffects) & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT))
815 {
816 if (fmt->dwEffects & CFE_SUBSCRIPT)
817 strcat(props, "\\sub");
818 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
819 strcat(props, "\\super");
820 else
821 strcat(props, "\\nosupersub");
822 }
823 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_UNDERLINE ||
824 old_fmt->bUnderlineType != fmt->bUnderlineType)
825 {
826 BYTE type = (fmt->dwEffects & CFE_UNDERLINE) ? fmt->bUnderlineType : CFU_UNDERLINENONE;
827 switch (type)
828 {
829 case CFU_UNDERLINE:
830 strcat(props, "\\ul");
831 break;
832 case CFU_UNDERLINEDOTTED:
833 strcat(props, "\\uld");
834 break;
835 case CFU_UNDERLINEDOUBLE:
836 strcat(props, "\\uldb");
837 break;
838 case CFU_UNDERLINEWORD:
839 strcat(props, "\\ulw");
840 break;
841 case CFU_CF1UNDERLINE:
842 case CFU_UNDERLINENONE:
843 default:
844 strcat(props, "\\ulnone");
845 break;
846 }
847 }
848
849 if (strcmpW(old_fmt->szFaceName, fmt->szFaceName) ||
850 old_fmt->bCharSet != fmt->bCharSet)
851 {
852 if (find_font_in_fonttbl( pStream, fmt, &i ))
853 {
854 sprintf(props + strlen(props), "\\f%u", i);
855
856 /* In UTF-8 mode, charsets/codepages are not used */
857 if (pStream->nDefaultCodePage != CP_UTF8)
858 {
859 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
860 pStream->nCodePage = pStream->nDefaultCodePage;
861 else
862 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
863 }
864 }
865 }
866 if (*props)
867 strcat(props, " ");
868 if (!ME_StreamOutPrint(pStream, props))
869 return FALSE;
870 *old_fmt = *fmt;
871 return TRUE;
872 }
873
874
875 static BOOL
876 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
877 {
878 char buffer[STREAMOUT_BUFFER_SIZE];
879 int pos = 0;
880 int fit, nBytes, i;
881
882 if (nChars == -1)
883 nChars = lstrlenW(text);
884
885 while (nChars) {
886 /* In UTF-8 mode, font charsets are not used. */
887 if (pStream->nDefaultCodePage == CP_UTF8) {
888 /* 6 is the maximum character length in UTF-8 */
889 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
890 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
891 STREAMOUT_BUFFER_SIZE, NULL, NULL);
892 nChars -= fit;
893 text += fit;
894 for (i = 0; i < nBytes; i++)
895 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
896 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
897 return FALSE;
898 pos = i;
899 }
900 if (pos < nBytes)
901 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
902 return FALSE;
903 pos = 0;
904 } else if (*text < 128) {
905 if (*text == '{' || *text == '}' || *text == '\\')
906 buffer[pos++] = '\\';
907 buffer[pos++] = (char)(*text++);
908 nChars--;
909 } else {
910 BOOL unknown = FALSE;
911 char letter[3];
912
913 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
914 * codepages including CP_SYMBOL for which the last parameter must be set
915 * to NULL for the function to succeed. But in Wine we need to care only
916 * about CP_SYMBOL */
917 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
918 letter, 3, NULL,
919 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
920 if (unknown)
921 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
922 else if ((BYTE)*letter < 128) {
923 if (*letter == '{' || *letter == '}' || *letter == '\\')
924 buffer[pos++] = '\\';
925 buffer[pos++] = *letter;
926 } else {
927 for (i = 0; i < nBytes; i++)
928 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
929 }
930 text++;
931 nChars--;
932 }
933 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
934 if (!ME_StreamOutMove(pStream, buffer, pos))
935 return FALSE;
936 pos = 0;
937 }
938 }
939 return ME_StreamOutMove(pStream, buffer, pos);
940 }
941
942 static BOOL stream_out_graphics( ME_TextEditor *editor, ME_OutStream *stream,
943 ME_Run *run )
944 {
945 IDataObject *data;
946 HRESULT hr;
947 FORMATETC fmt = { CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF };
948 STGMEDIUM med = { TYMED_NULL };
949 BOOL ret = FALSE;
950 ENHMETAHEADER *emf_bits = NULL;
951 UINT size;
952 SIZE goal, pic;
953 ME_Context c;
954
955 hr = IOleObject_QueryInterface( run->ole_obj->poleobj, &IID_IDataObject, (void **)&data );
956 if (FAILED(hr)) return FALSE;
957
958 ME_InitContext( &c, editor, ITextHost_TxGetDC( editor->texthost ) );
959 hr = IDataObject_QueryGetData( data, &fmt );
960 if (hr != S_OK) goto done;
961
962 hr = IDataObject_GetData( data, &fmt, &med );
963 if (FAILED(hr)) goto done;
964 if (med.tymed != TYMED_ENHMF) goto done;
965
966 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, 0, NULL );
967 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
968
969 emf_bits = HeapAlloc( GetProcessHeap(), 0, size );
970 if (!emf_bits) goto done;
971
972 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, size, (BYTE *)emf_bits );
973 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
974
975 /* size_in_pixels = (frame_size / 100) * szlDevice / szlMillimeters
976 pic = size_in_pixels * 2540 / dpi */
977 pic.cx = MulDiv( emf_bits->rclFrame.right - emf_bits->rclFrame.left, emf_bits->szlDevice.cx * 254,
978 emf_bits->szlMillimeters.cx * c.dpi.cx * 10 );
979 pic.cy = MulDiv( emf_bits->rclFrame.bottom - emf_bits->rclFrame.top, emf_bits->szlDevice.cy * 254,
980 emf_bits->szlMillimeters.cy * c.dpi.cy * 10 );
981
982 /* convert goal size to twips */
983 goal.cx = MulDiv( run->ole_obj->sizel.cx, 144, 254 );
984 goal.cy = MulDiv( run->ole_obj->sizel.cy, 144, 254 );
985
986 if (!ME_StreamOutPrint( stream, "{\\*\\shppict{\\pict\\emfblip\\picw%d\\pich%d\\picwgoal%d\\pichgoal%d\n",
987 pic.cx, pic.cy, goal.cx, goal.cy ))
988 goto done;
989
990 if (!ME_StreamOutHexData( stream, (BYTE *)emf_bits, size ))
991 goto done;
992
993 if (!ME_StreamOutPrint( stream, "}}\n" ))
994 goto done;
995
996 ret = TRUE;
997
998 done:
999 ME_DestroyContext( &c );
1000 HeapFree( GetProcessHeap(), 0, emf_bits );
1001 ReleaseStgMedium( &med );
1002 IDataObject_Release( data );
1003 return ret;
1004 }
1005
1006 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
1007 const ME_Cursor *start, int nChars, int dwFormat)
1008 {
1009 ME_Cursor cursor = *start;
1010 ME_DisplayItem *prev_para = NULL;
1011 ME_Cursor endCur = cursor;
1012
1013 ME_MoveCursorChars(editor, &endCur, nChars, TRUE);
1014
1015 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
1016 return FALSE;
1017
1018 if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun))
1019 return FALSE;
1020
1021 /* TODO: stylesheet table */
1022
1023 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0;}"))
1024 return FALSE;
1025
1026 /* TODO: information group */
1027
1028 /* TODO: document formatting properties */
1029
1030 /* FIXME: We have only one document section */
1031
1032 /* TODO: section formatting properties */
1033
1034 do {
1035 if (cursor.pPara != prev_para)
1036 {
1037 prev_para = cursor.pPara;
1038 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
1039 return FALSE;
1040 }
1041
1042 if (cursor.pRun == endCur.pRun && !endCur.nOffset)
1043 break;
1044 TRACE("flags %xh\n", cursor.pRun->member.run.nFlags);
1045 /* TODO: emit embedded objects */
1046 if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
1047 continue;
1048 if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) {
1049 if (!stream_out_graphics(editor, pStream, &cursor.pRun->member.run))
1050 return FALSE;
1051 } else if (cursor.pRun->member.run.nFlags & MERF_TAB) {
1052 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
1053 cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1054 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE)
1055 {
1056 if (!ME_StreamOutPrint(pStream, "\\cell "))
1057 return FALSE;
1058 } else {
1059 if (!ME_StreamOutPrint(pStream, "\\tab "))
1060 return FALSE;
1061 }
1062 } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) {
1063 if (pStream->nNestingLevel > 1) {
1064 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
1065 return FALSE;
1066 } else {
1067 if (!ME_StreamOutPrint(pStream, "\\cell "))
1068 return FALSE;
1069 }
1070 nChars--;
1071 } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) {
1072 if (cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1073 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE &&
1074 !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
1075 {
1076 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
1077 return FALSE;
1078 } else {
1079 if (!ME_StreamOutPrint(pStream, "\\par\r\n"))
1080 return FALSE;
1081 }
1082 /* Skip as many characters as required by current line break */
1083 nChars = max(0, nChars - cursor.pRun->member.run.len);
1084 } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) {
1085 if (!ME_StreamOutPrint(pStream, "\\line\r\n"))
1086 return FALSE;
1087 nChars--;
1088 } else {
1089 int nEnd;
1090
1091 TRACE("style %p\n", cursor.pRun->member.run.style);
1092 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
1093 return FALSE;
1094
1095 nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len;
1096 if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1097 nEnd - cursor.nOffset))
1098 return FALSE;
1099 cursor.nOffset = 0;
1100 }
1101 } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE));
1102
1103 if (!ME_StreamOutMove(pStream, "}\0", 2))
1104 return FALSE;
1105 return TRUE;
1106 }
1107
1108
1109 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
1110 const ME_Cursor *start, int nChars, DWORD dwFormat)
1111 {
1112 ME_Cursor cursor = *start;
1113 int nLen;
1114 UINT nCodePage = CP_ACP;
1115 char *buffer = NULL;
1116 int nBufLen = 0;
1117 BOOL success = TRUE;
1118
1119 if (!cursor.pRun)
1120 return FALSE;
1121
1122 if (dwFormat & SF_USECODEPAGE)
1123 nCodePage = HIWORD(dwFormat);
1124
1125 /* TODO: Handle SF_TEXTIZED */
1126
1127 while (success && nChars && cursor.pRun) {
1128 nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset);
1129
1130 if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA)
1131 {
1132 static const WCHAR szEOL[] = { '\r', '\n' };
1133
1134 /* richedit 2.0 - all line breaks are \r\n */
1135 if (dwFormat & SF_UNICODE)
1136 success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
1137 else
1138 success = ME_StreamOutMove(pStream, "\r\n", 2);
1139 } else {
1140 if (dwFormat & SF_UNICODE)
1141 success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )),
1142 sizeof(WCHAR) * nLen);
1143 else {
1144 int nSize;
1145
1146 nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1147 nLen, NULL, 0, NULL, NULL);
1148 if (nSize > nBufLen) {
1149 FREE_OBJ(buffer);
1150 buffer = ALLOC_N_OBJ(char, nSize);
1151 nBufLen = nSize;
1152 }
1153 WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1154 nLen, buffer, nSize, NULL, NULL);
1155 success = ME_StreamOutMove(pStream, buffer, nSize);
1156 }
1157 }
1158
1159 nChars -= nLen;
1160 cursor.nOffset = 0;
1161 cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun);
1162 }
1163
1164 FREE_OBJ(buffer);
1165 return success;
1166 }
1167
1168
1169 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
1170 const ME_Cursor *start,
1171 int nChars, EDITSTREAM *stream)
1172 {
1173 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
1174
1175 if (dwFormat & SF_RTF)
1176 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
1177 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
1178 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
1179 if (!pStream->stream->dwError)
1180 ME_StreamOutFlush(pStream);
1181 return ME_StreamOutFree(pStream);
1182 }
1183
1184 LRESULT
1185 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
1186 {
1187 ME_Cursor start;
1188 int nChars;
1189
1190 if (dwFormat & SFF_SELECTION) {
1191 int nStart, nTo;
1192 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
1193 nChars = nTo - nStart;
1194 } else {
1195 ME_SetCursorToStart(editor, &start);
1196 nChars = ME_GetTextLength(editor);
1197 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
1198 if (dwFormat & SF_RTF)
1199 nChars++;
1200 }
1201 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);
1202 }