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