607ef6d8289ae20b41c347fbd7920831b0d84456
[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 /* Output the color table */
381 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
382 for (i = 1; i < pStream->nColorTblLen; i++)
383 {
384 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
385 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
386 return FALSE;
387 }
388 if (!ME_StreamOutPrint(pStream, "}\r\n")) return FALSE;
389
390 return TRUE;
391 }
392
393 static BOOL
394 ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
395 ME_DisplayItem *para)
396 {
397 ME_DisplayItem *cell;
398 char props[STREAMOUT_BUFFER_SIZE] = "";
399 int i;
400 const char sideChar[4] = {'t','l','b','r'};
401
402 if (!ME_StreamOutPrint(pStream, "\\trowd"))
403 return FALSE;
404 if (!editor->bEmulateVersion10) { /* v4.1 */
405 PARAFORMAT2 *pFmt = &ME_GetTableRowEnd(para)->member.para.fmt;
406 para = ME_GetTableRowStart(para);
407 cell = para->member.para.next_para->member.para.pCell;
408 assert(cell);
409 if (pFmt->dxOffset)
410 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
411 if (pFmt->dxStartIndent)
412 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
413 do {
414 ME_Border* borders[4] = { &cell->member.cell.border.top,
415 &cell->member.cell.border.left,
416 &cell->member.cell.border.bottom,
417 &cell->member.cell.border.right };
418 for (i = 0; i < 4; i++)
419 {
420 if (borders[i]->width)
421 {
422 unsigned int idx;
423 COLORREF crColor = borders[i]->colorRef;
424 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
425 sprintf(props + strlen(props), "\\brdrs");
426 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
427 if (find_color_in_colortbl( pStream, crColor, &idx ))
428 sprintf(props + strlen(props), "\\brdrcf%u", idx);
429 }
430 }
431 sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
432 cell = cell->member.cell.next_cell;
433 } while (cell->member.cell.next_cell);
434 } else { /* v1.0 - 3.0 */
435 const ME_Border* borders[4] = { &para->member.para.border.top,
436 &para->member.para.border.left,
437 &para->member.para.border.bottom,
438 &para->member.para.border.right };
439 PARAFORMAT2 *pFmt = &para->member.para.fmt;
440
441 assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
442 if (pFmt->dxOffset)
443 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
444 if (pFmt->dxStartIndent)
445 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
446 for (i = 0; i < 4; i++)
447 {
448 if (borders[i]->width)
449 {
450 unsigned int idx;
451 COLORREF crColor = borders[i]->colorRef;
452 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
453 sprintf(props + strlen(props), "\\brdrs");
454 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
455 if (find_color_in_colortbl( pStream, crColor, &idx ))
456 sprintf(props + strlen(props), "\\brdrcf%u", idx);
457 }
458 }
459 for (i = 0; i < pFmt->cTabCount; i++)
460 {
461 sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
462 }
463 }
464 if (!ME_StreamOutPrint(pStream, props))
465 return FALSE;
466 props[0] = '\0';
467 return TRUE;
468 }
469
470 static BOOL stream_out_para_num( ME_OutStream *stream, ME_Paragraph *para, BOOL pn_dest )
471 {
472 static const char fmt_label[] = "{\\*\\pn\\pnlvlbody\\pnf%u\\pnindent%d\\pnstart%d%s%s}";
473 static const char fmt_bullet[] = "{\\*\\pn\\pnlvlblt\\pnf%u\\pnindent%d{\\pntxtb\\'b7}}";
474 static const char dec[] = "\\pndec";
475 static const char lcltr[] = "\\pnlcltr";
476 static const char ucltr[] = "\\pnucltr";
477 static const char lcrm[] = "\\pnlcrm";
478 static const char ucrm[] = "\\pnucrm";
479 static const char period[] = "{\\pntxta.}";
480 static const char paren[] = "{\\pntxta)}";
481 static const char parens[] = "{\\pntxtb(}{\\pntxta)}";
482 const char *type, *style = "";
483 unsigned int idx;
484
485 find_font_in_fonttbl( stream, &para->para_num.style->fmt, &idx );
486
487 if (!ME_StreamOutPrint( stream, "{\\pntext\\f%u ", idx )) return FALSE;
488 if (!ME_StreamOutRTFText( stream, para->para_num.text->szData, para->para_num.text->nLen ))
489 return FALSE;
490 if (!ME_StreamOutPrint( stream, "\\tab}" )) return FALSE;
491
492 if (!pn_dest) return TRUE;
493
494 if (para->fmt.wNumbering == PFN_BULLET)
495 {
496 if (!ME_StreamOutPrint( stream, fmt_bullet, idx, para->fmt.wNumberingTab ))
497 return FALSE;
498 }
499 else
500 {
501 switch (para->fmt.wNumbering)
502 {
503 case PFN_ARABIC:
504 default:
505 type = dec;
506 break;
507 case PFN_LCLETTER:
508 type = lcltr;
509 break;
510 case PFN_UCLETTER:
511 type = ucltr;
512 break;
513 case PFN_LCROMAN:
514 type = lcrm;
515 break;
516 case PFN_UCROMAN:
517 type = ucrm;
518 break;
519 }
520 switch (para->fmt.wNumberingStyle & 0xf00)
521 {
522 case PFNS_PERIOD:
523 style = period;
524 break;
525 case PFNS_PAREN:
526 style = paren;
527 break;
528 case PFNS_PARENS:
529 style = parens;
530 break;
531 }
532
533 if (!ME_StreamOutPrint( stream, fmt_label, idx, para->fmt.wNumberingTab,
534 para->fmt.wNumberingStart, type, style ))
535 return FALSE;
536 }
537 return TRUE;
538 }
539
540 static BOOL
541 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
542 ME_DisplayItem *para)
543 {
544 PARAFORMAT2 *fmt = &para->member.para.fmt;
545 char props[STREAMOUT_BUFFER_SIZE] = "";
546 int i;
547 ME_Paragraph *prev_para = NULL;
548
549 if (para->member.para.prev_para->type == diParagraph)
550 prev_para = &para->member.para.prev_para->member.para;
551
552 if (!editor->bEmulateVersion10) { /* v4.1 */
553 if (para->member.para.nFlags & MEPF_ROWSTART) {
554 pStream->nNestingLevel++;
555 if (pStream->nNestingLevel == 1) {
556 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
557 return FALSE;
558 }
559 return TRUE;
560 } else if (para->member.para.nFlags & MEPF_ROWEND) {
561 pStream->nNestingLevel--;
562 if (pStream->nNestingLevel >= 1) {
563 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
564 return FALSE;
565 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
566 return FALSE;
567 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
568 return FALSE;
569 } else {
570 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
571 return FALSE;
572 }
573 return TRUE;
574 }
575 } else { /* v1.0 - 3.0 */
576 if (para->member.para.fmt.dwMask & PFM_TABLE &&
577 para->member.para.fmt.wEffects & PFE_TABLE)
578 {
579 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
580 return FALSE;
581 }
582 }
583
584 if (prev_para && !memcmp( fmt, &prev_para->fmt, sizeof(*fmt) ))
585 {
586 if (fmt->wNumbering)
587 return stream_out_para_num( pStream, &para->member.para, FALSE );
588 return TRUE;
589 }
590
591 if (!ME_StreamOutPrint(pStream, "\\pard"))
592 return FALSE;
593
594 if (fmt->wNumbering)
595 if (!stream_out_para_num( pStream, &para->member.para, TRUE )) return FALSE;
596
597 if (!editor->bEmulateVersion10) { /* v4.1 */
598 if (pStream->nNestingLevel > 0)
599 strcat(props, "\\intbl");
600 if (pStream->nNestingLevel > 1)
601 sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
602 } else { /* v1.0 - 3.0 */
603 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
604 strcat(props, "\\intbl");
605 }
606
607 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
608 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
609 * set very different from the documentation.
610 * (Tested with RichEdit 5.50.25.0601) */
611
612 if (fmt->dwMask & PFM_ALIGNMENT) {
613 switch (fmt->wAlignment) {
614 case PFA_LEFT:
615 /* Default alignment: not emitted */
616 break;
617 case PFA_RIGHT:
618 strcat(props, "\\qr");
619 break;
620 case PFA_CENTER:
621 strcat(props, "\\qc");
622 break;
623 case PFA_JUSTIFY:
624 strcat(props, "\\qj");
625 break;
626 }
627 }
628
629 if (fmt->dwMask & PFM_LINESPACING) {
630 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
631 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
632 switch (fmt->bLineSpacingRule) {
633 case 0: /* Single spacing */
634 strcat(props, "\\sl-240\\slmult1");
635 break;
636 case 1: /* 1.5 spacing */
637 strcat(props, "\\sl-360\\slmult1");
638 break;
639 case 2: /* Double spacing */
640 strcat(props, "\\sl-480\\slmult1");
641 break;
642 case 3:
643 sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
644 break;
645 case 4:
646 sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
647 break;
648 case 5:
649 sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
650 break;
651 }
652 }
653
654 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
655 strcat(props, "\\hyph0");
656 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
657 strcat(props, "\\keep");
658 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
659 strcat(props, "\\keepn");
660 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
661 strcat(props, "\\noline");
662 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
663 strcat(props, "\\nowidctlpar");
664 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
665 strcat(props, "\\pagebb");
666 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
667 strcat(props, "\\rtlpar");
668 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
669 strcat(props, "\\sbys");
670
671 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
672 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
673 {
674 if (fmt->dxOffset)
675 sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
676 if (fmt->dxStartIndent)
677 sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
678 if (fmt->dxRightIndent)
679 sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
680 if (fmt->dwMask & PFM_TABSTOPS) {
681 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
682
683 for (i = 0; i < fmt->cTabCount; i++) {
684 switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
685 case 1:
686 strcat(props, "\\tqc");
687 break;
688 case 2:
689 strcat(props, "\\tqr");
690 break;
691 case 3:
692 strcat(props, "\\tqdec");
693 break;
694 case 4:
695 /* Word bar tab (vertical bar). Handled below */
696 break;
697 }
698 if (fmt->rgxTabs[i] >> 28 <= 5)
699 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
700 sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
701 }
702 }
703 }
704 if (fmt->dySpaceAfter)
705 sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
706 if (fmt->dySpaceBefore)
707 sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
708 if (fmt->sStyle != -1)
709 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
710
711 if (fmt->dwMask & PFM_SHADING) {
712 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
713 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
714 "\\bghoriz", "\\bgvert", "\\bgfdiag",
715 "\\bgbdiag", "\\bgcross", "\\bgdcross",
716 "", "", "" };
717 if (fmt->wShadingWeight)
718 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
719 if (fmt->wShadingStyle & 0xF)
720 strcat(props, style[fmt->wShadingStyle & 0xF]);
721 if ((fmt->wShadingStyle >> 4) & 0xf)
722 sprintf(props + strlen(props), "\\cfpat%d", (fmt->wShadingStyle >> 4) & 0xf);
723 if ((fmt->wShadingStyle >> 8) & 0xf)
724 sprintf(props + strlen(props), "\\cbpat%d", (fmt->wShadingStyle >> 8) & 0xf);
725 }
726 if (*props)
727 strcat(props, " ");
728
729 if (*props && !ME_StreamOutPrint(pStream, props))
730 return FALSE;
731
732 return TRUE;
733 }
734
735
736 static BOOL
737 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
738 {
739 char props[STREAMOUT_BUFFER_SIZE] = "";
740 unsigned int i;
741 CHARFORMAT2W *old_fmt = &pStream->cur_fmt;
742 static const struct
743 {
744 DWORD effect;
745 const char *on, *off;
746 } effects[] =
747 {
748 { CFE_ALLCAPS, "\\caps", "\\caps0" },
749 { CFE_BOLD, "\\b", "\\b0" },
750 { CFE_DISABLED, "\\disabled", "\\disabled0" },
751 { CFE_EMBOSS, "\\embo", "\\embo0" },
752 { CFE_HIDDEN, "\\v", "\\v0" },
753 { CFE_IMPRINT, "\\impr", "\\impr0" },
754 { CFE_ITALIC, "\\i", "\\i0" },
755 { CFE_OUTLINE, "\\outl", "\\outl0" },
756 { CFE_PROTECTED, "\\protect", "\\protect0" },
757 { CFE_SHADOW, "\\shad", "\\shad0" },
758 { CFE_SMALLCAPS, "\\scaps", "\\scaps0" },
759 { CFE_STRIKEOUT, "\\strike", "\\strike0" },
760 };
761
762 for (i = 0; i < sizeof(effects) / sizeof(effects[0]); i++)
763 {
764 if ((old_fmt->dwEffects ^ fmt->dwEffects) & effects[i].effect)
765 strcat( props, fmt->dwEffects & effects[i].effect ? effects[i].on : effects[i].off );
766 }
767
768 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOBACKCOLOR ||
769 (!(fmt->dwEffects & CFE_AUTOBACKCOLOR) && old_fmt->crBackColor != fmt->crBackColor))
770 {
771 if (fmt->dwEffects & CFE_AUTOBACKCOLOR) i = 0;
772 else find_color_in_colortbl( pStream, fmt->crBackColor, &i );
773 sprintf(props + strlen(props), "\\cb%u", i);
774 }
775 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOCOLOR ||
776 (!(fmt->dwEffects & CFE_AUTOCOLOR) && old_fmt->crTextColor != fmt->crTextColor))
777 {
778 if (fmt->dwEffects & CFE_AUTOCOLOR) i = 0;
779 else find_color_in_colortbl( pStream, fmt->crTextColor, &i );
780 sprintf(props + strlen(props), "\\cf%u", i);
781 }
782
783 if (old_fmt->bAnimation != fmt->bAnimation)
784 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
785 if (old_fmt->wKerning != fmt->wKerning)
786 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
787
788 if (old_fmt->lcid != fmt->lcid)
789 {
790 /* TODO: handle SFF_PLAINRTF */
791 if (LOWORD(fmt->lcid) == 1024)
792 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
793 else
794 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
795 }
796
797 if (old_fmt->yOffset != fmt->yOffset)
798 {
799 if (fmt->yOffset >= 0)
800 sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
801 else
802 sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
803 }
804 if (old_fmt->yHeight != fmt->yHeight)
805 sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
806 if (old_fmt->sSpacing != fmt->sSpacing)
807 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
808 if ((old_fmt->dwEffects ^ fmt->dwEffects) & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT))
809 {
810 if (fmt->dwEffects & CFE_SUBSCRIPT)
811 strcat(props, "\\sub");
812 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
813 strcat(props, "\\super");
814 else
815 strcat(props, "\\nosupersub");
816 }
817 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_UNDERLINE ||
818 old_fmt->bUnderlineType != fmt->bUnderlineType)
819 {
820 BYTE type = (fmt->dwEffects & CFE_UNDERLINE) ? fmt->bUnderlineType : CFU_UNDERLINENONE;
821 switch (type)
822 {
823 case CFU_UNDERLINE:
824 strcat(props, "\\ul");
825 break;
826 case CFU_UNDERLINEDOTTED:
827 strcat(props, "\\uld");
828 break;
829 case CFU_UNDERLINEDOUBLE:
830 strcat(props, "\\uldb");
831 break;
832 case CFU_UNDERLINEWORD:
833 strcat(props, "\\ulw");
834 break;
835 case CFU_CF1UNDERLINE:
836 case CFU_UNDERLINENONE:
837 default:
838 strcat(props, "\\ulnone");
839 break;
840 }
841 }
842
843 if (strcmpW(old_fmt->szFaceName, fmt->szFaceName) ||
844 old_fmt->bCharSet != fmt->bCharSet)
845 {
846 if (find_font_in_fonttbl( pStream, fmt, &i ))
847 {
848 sprintf(props + strlen(props), "\\f%u", i);
849
850 /* In UTF-8 mode, charsets/codepages are not used */
851 if (pStream->nDefaultCodePage != CP_UTF8)
852 {
853 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
854 pStream->nCodePage = pStream->nDefaultCodePage;
855 else
856 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
857 }
858 }
859 }
860 if (*props)
861 strcat(props, " ");
862 if (!ME_StreamOutPrint(pStream, props))
863 return FALSE;
864 *old_fmt = *fmt;
865 return TRUE;
866 }
867
868
869 static BOOL
870 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
871 {
872 char buffer[STREAMOUT_BUFFER_SIZE];
873 int pos = 0;
874 int fit, nBytes, i;
875
876 if (nChars == -1)
877 nChars = lstrlenW(text);
878
879 while (nChars) {
880 /* In UTF-8 mode, font charsets are not used. */
881 if (pStream->nDefaultCodePage == CP_UTF8) {
882 /* 6 is the maximum character length in UTF-8 */
883 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
884 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
885 STREAMOUT_BUFFER_SIZE, NULL, NULL);
886 nChars -= fit;
887 text += fit;
888 for (i = 0; i < nBytes; i++)
889 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
890 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
891 return FALSE;
892 pos = i;
893 }
894 if (pos < nBytes)
895 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
896 return FALSE;
897 pos = 0;
898 } else if (*text < 128) {
899 if (*text == '{' || *text == '}' || *text == '\\')
900 buffer[pos++] = '\\';
901 buffer[pos++] = (char)(*text++);
902 nChars--;
903 } else {
904 BOOL unknown = FALSE;
905 char letter[3];
906
907 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
908 * codepages including CP_SYMBOL for which the last parameter must be set
909 * to NULL for the function to succeed. But in Wine we need to care only
910 * about CP_SYMBOL */
911 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
912 letter, 3, NULL,
913 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
914 if (unknown)
915 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
916 else if ((BYTE)*letter < 128) {
917 if (*letter == '{' || *letter == '}' || *letter == '\\')
918 buffer[pos++] = '\\';
919 buffer[pos++] = *letter;
920 } else {
921 for (i = 0; i < nBytes; i++)
922 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
923 }
924 text++;
925 nChars--;
926 }
927 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
928 if (!ME_StreamOutMove(pStream, buffer, pos))
929 return FALSE;
930 pos = 0;
931 }
932 }
933 return ME_StreamOutMove(pStream, buffer, pos);
934 }
935
936 static BOOL stream_out_graphics( ME_TextEditor *editor, ME_OutStream *stream,
937 ME_Run *run )
938 {
939 IDataObject *data;
940 HRESULT hr;
941 FORMATETC fmt = { CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF };
942 STGMEDIUM med = { TYMED_NULL };
943 BOOL ret = FALSE;
944 ENHMETAHEADER *emf_bits = NULL;
945 UINT size;
946 SIZE goal, pic;
947 ME_Context c;
948
949 hr = IOleObject_QueryInterface( run->ole_obj->poleobj, &IID_IDataObject, (void **)&data );
950 if (FAILED(hr)) return FALSE;
951
952 ME_InitContext( &c, editor, ITextHost_TxGetDC( editor->texthost ) );
953 hr = IDataObject_QueryGetData( data, &fmt );
954 if (hr != S_OK) goto done;
955
956 hr = IDataObject_GetData( data, &fmt, &med );
957 if (FAILED(hr)) goto done;
958 if (med.tymed != TYMED_ENHMF) goto done;
959
960 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, 0, NULL );
961 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
962
963 emf_bits = HeapAlloc( GetProcessHeap(), 0, size );
964 if (!emf_bits) goto done;
965
966 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, size, (BYTE *)emf_bits );
967 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
968
969 /* size_in_pixels = (frame_size / 100) * szlDevice / szlMillimeters
970 pic = size_in_pixels * 2540 / dpi */
971 pic.cx = MulDiv( emf_bits->rclFrame.right - emf_bits->rclFrame.left, emf_bits->szlDevice.cx * 254,
972 emf_bits->szlMillimeters.cx * c.dpi.cx * 10 );
973 pic.cy = MulDiv( emf_bits->rclFrame.bottom - emf_bits->rclFrame.top, emf_bits->szlDevice.cy * 254,
974 emf_bits->szlMillimeters.cy * c.dpi.cy * 10 );
975
976 /* convert goal size to twips */
977 goal.cx = MulDiv( run->ole_obj->sizel.cx, 144, 254 );
978 goal.cy = MulDiv( run->ole_obj->sizel.cy, 144, 254 );
979
980 if (!ME_StreamOutPrint( stream, "{\\*\\shppict{\\pict\\emfblip\\picw%d\\pich%d\\picwgoal%d\\pichgoal%d\n",
981 pic.cx, pic.cy, goal.cx, goal.cy ))
982 goto done;
983
984 if (!ME_StreamOutHexData( stream, (BYTE *)emf_bits, size ))
985 goto done;
986
987 if (!ME_StreamOutPrint( stream, "}}\n" ))
988 goto done;
989
990 ret = TRUE;
991
992 done:
993 ME_DestroyContext( &c );
994 HeapFree( GetProcessHeap(), 0, emf_bits );
995 ReleaseStgMedium( &med );
996 IDataObject_Release( data );
997 return ret;
998 }
999
1000 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
1001 const ME_Cursor *start, int nChars, int dwFormat)
1002 {
1003 ME_Cursor cursor = *start;
1004 ME_DisplayItem *prev_para = NULL;
1005 ME_Cursor endCur = cursor;
1006
1007 ME_MoveCursorChars(editor, &endCur, nChars, TRUE);
1008
1009 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
1010 return FALSE;
1011
1012 if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun))
1013 return FALSE;
1014
1015 /* TODO: stylesheet table */
1016
1017 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0;}\r\n"))
1018 return FALSE;
1019
1020 /* TODO: information group */
1021
1022 /* TODO: document formatting properties */
1023
1024 /* FIXME: We have only one document section */
1025
1026 /* TODO: section formatting properties */
1027
1028 do {
1029 if (cursor.pPara != prev_para)
1030 {
1031 prev_para = cursor.pPara;
1032 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
1033 return FALSE;
1034 }
1035
1036 if (cursor.pRun == endCur.pRun && !endCur.nOffset)
1037 break;
1038 TRACE("flags %xh\n", cursor.pRun->member.run.nFlags);
1039 /* TODO: emit embedded objects */
1040 if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
1041 continue;
1042 if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) {
1043 if (!stream_out_graphics(editor, pStream, &cursor.pRun->member.run))
1044 return FALSE;
1045 } else if (cursor.pRun->member.run.nFlags & MERF_TAB) {
1046 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
1047 cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1048 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE)
1049 {
1050 if (!ME_StreamOutPrint(pStream, "\\cell "))
1051 return FALSE;
1052 } else {
1053 if (!ME_StreamOutPrint(pStream, "\\tab "))
1054 return FALSE;
1055 }
1056 } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) {
1057 if (pStream->nNestingLevel > 1) {
1058 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
1059 return FALSE;
1060 } else {
1061 if (!ME_StreamOutPrint(pStream, "\\cell "))
1062 return FALSE;
1063 }
1064 nChars--;
1065 } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) {
1066 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
1067 return FALSE;
1068
1069 if (cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1070 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE &&
1071 !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
1072 {
1073 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
1074 return FALSE;
1075 } else {
1076 if (!ME_StreamOutPrint(pStream, "\\par\r\n"))
1077 return FALSE;
1078 }
1079 /* Skip as many characters as required by current line break */
1080 nChars = max(0, nChars - cursor.pRun->member.run.len);
1081 } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) {
1082 if (!ME_StreamOutPrint(pStream, "\\line\r\n"))
1083 return FALSE;
1084 nChars--;
1085 } else {
1086 int nEnd;
1087
1088 TRACE("style %p\n", cursor.pRun->member.run.style);
1089 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
1090 return FALSE;
1091
1092 nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len;
1093 if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1094 nEnd - cursor.nOffset))
1095 return FALSE;
1096 cursor.nOffset = 0;
1097 }
1098 } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE));
1099
1100 if (!ME_StreamOutMove(pStream, "}\0", 2))
1101 return FALSE;
1102 return TRUE;
1103 }
1104
1105
1106 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
1107 const ME_Cursor *start, int nChars, DWORD dwFormat)
1108 {
1109 ME_Cursor cursor = *start;
1110 int nLen;
1111 UINT nCodePage = CP_ACP;
1112 char *buffer = NULL;
1113 int nBufLen = 0;
1114 BOOL success = TRUE;
1115
1116 if (!cursor.pRun)
1117 return FALSE;
1118
1119 if (dwFormat & SF_USECODEPAGE)
1120 nCodePage = HIWORD(dwFormat);
1121
1122 /* TODO: Handle SF_TEXTIZED */
1123
1124 while (success && nChars && cursor.pRun) {
1125 nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset);
1126
1127 if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA)
1128 {
1129 static const WCHAR szEOL[] = { '\r', '\n' };
1130
1131 /* richedit 2.0 - all line breaks are \r\n */
1132 if (dwFormat & SF_UNICODE)
1133 success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
1134 else
1135 success = ME_StreamOutMove(pStream, "\r\n", 2);
1136 } else {
1137 if (dwFormat & SF_UNICODE)
1138 success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )),
1139 sizeof(WCHAR) * nLen);
1140 else {
1141 int nSize;
1142
1143 nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1144 nLen, NULL, 0, NULL, NULL);
1145 if (nSize > nBufLen) {
1146 FREE_OBJ(buffer);
1147 buffer = ALLOC_N_OBJ(char, nSize);
1148 nBufLen = nSize;
1149 }
1150 WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1151 nLen, buffer, nSize, NULL, NULL);
1152 success = ME_StreamOutMove(pStream, buffer, nSize);
1153 }
1154 }
1155
1156 nChars -= nLen;
1157 cursor.nOffset = 0;
1158 cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun);
1159 }
1160
1161 FREE_OBJ(buffer);
1162 return success;
1163 }
1164
1165
1166 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
1167 const ME_Cursor *start,
1168 int nChars, EDITSTREAM *stream)
1169 {
1170 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
1171
1172 if (dwFormat & SF_RTF)
1173 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
1174 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
1175 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
1176 if (!pStream->stream->dwError)
1177 ME_StreamOutFlush(pStream);
1178 return ME_StreamOutFree(pStream);
1179 }
1180
1181 LRESULT
1182 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
1183 {
1184 ME_Cursor start;
1185 int nChars;
1186
1187 if (dwFormat & SFF_SELECTION) {
1188 int nStart, nTo;
1189 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
1190 nChars = nTo - nStart;
1191 } else {
1192 ME_SetCursorToStart(editor, &start);
1193 nChars = ME_GetTextLength(editor);
1194 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
1195 if (dwFormat & SF_RTF)
1196 nChars++;
1197 }
1198 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);
1199 }