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