writer.c 30.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * RichEdit - RTF writer module
 *
 * Copyright 2005 by Phil Krylov
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 22 23
#include "config.h"
#include "wine/port.h"

24
#include "editor.h"
25
#include "rtf.h"
26 27 28 29

WINE_DEFAULT_DEBUG_CHANNEL(richedit);


Phil Krylov's avatar
Phil Krylov committed
30
static BOOL
31
ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
Phil Krylov's avatar
Phil Krylov committed
32 33


34
static ME_OutStream*
35 36
ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
{
37 38 39 40 41 42 43
  ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream);
  pStream->stream = stream;
  pStream->stream->dwError = 0;
  pStream->pos = 0;
  pStream->written = 0;
  pStream->nFontTblLen = 0;
  pStream->nColorTblLen = 1;
44
  pStream->nNestingLevel = 0;
45
  return pStream;
46 47 48 49
}


static BOOL
50
ME_StreamOutFlush(ME_OutStream *pStream)
51 52 53
{
  LONG nStart = 0;
  LONG nWritten = 0;
54
  LONG nRemaining = 0;
55
  EDITSTREAM *stream = pStream->stream;
56 57

  do {
58
    TRACE("sending %u bytes\n", pStream->pos - nStart);
59 60
    /* Some apps seem not to set *pcb unless a problem arises, relying
      on initial random nWritten value, which is usually >STREAMOUT_BUFFER_SIZE */
61
    nRemaining = pStream->pos - nStart;
62
    nWritten = 0xDEADBEEF;
63 64
    stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer + nStart,
                                          pStream->pos - nStart, &nWritten);
65
    TRACE("error=%u written=%u\n", stream->dwError, nWritten);
66
    if (nWritten > (pStream->pos - nStart) || nWritten<0) {
67
      FIXME("Invalid returned written size *pcb: 0x%x (%d) instead of %d\n", 
68 69 70
            (unsigned)nWritten, nWritten, nRemaining);
      nWritten = nRemaining;
    }
71 72
    if (nWritten == 0 || stream->dwError)
      return FALSE;
73
    pStream->written += nWritten;
74
    nStart += nWritten;
75 76
  } while (nStart < pStream->pos);
  pStream->pos = 0;
77 78 79 80 81
  return TRUE;
}


static LONG
82
ME_StreamOutFree(ME_OutStream *pStream)
83
{
84
  LONG written = pStream->written;
85
  TRACE("total length = %u\n", written);
86

87
  FREE_OBJ(pStream);
88 89 90 91 92
  return written;
}


static BOOL
93
ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
94 95 96 97 98
{
  while (len) {
    int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
    int fit = min(space, len);

99
    TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
100 101 102 103 104
    memmove(pStream->buffer + pStream->pos, buffer, fit);
    len -= fit;
    buffer += fit;
    pStream->pos += fit;
    if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
105
      if (!ME_StreamOutFlush(pStream))
106 107 108 109 110 111 112 113
        return FALSE;
    }
  }
  return TRUE;
}


static BOOL
114
ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
115 116 117 118 119 120 121 122 123
{
  char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
  int len;
  va_list valist;

  va_start(valist, format);
  len = vsnprintf(string, sizeof(string), format, valist);
  va_end(valist);
  
124
  return ME_StreamOutMove(pStream, string, len);
125 126 127 128
}


static BOOL
129
ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
130
{
131
  const char *cCharSet = NULL;
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  UINT nCodePage;
  LANGID language;
  BOOL success;
  
  if (dwFormat & SF_USECODEPAGE) {
    CPINFOEXW info;
    
    switch (HIWORD(dwFormat)) {
      case CP_ACP:
        cCharSet = "ansi";
        nCodePage = GetACP();
        break;
      case CP_OEMCP:
        nCodePage = GetOEMCP();
        if (nCodePage == 437)
          cCharSet = "pc";
        else if (nCodePage == 850)
          cCharSet = "pca";
        else
          cCharSet = "ansi";
        break;
      case CP_UTF8:
        nCodePage = CP_UTF8;
        break;
      default:
        if (HIWORD(dwFormat) == CP_MACCP) {
          cCharSet = "mac";
          nCodePage = 10000; /* MacRoman */
        } else {
          cCharSet = "ansi";
          nCodePage = 1252; /* Latin-1 */
        }
        if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
          nCodePage = info.CodePage;
    }
  } else {
    cCharSet = "ansi";
169 170 171 172
    /* TODO: If the original document contained an \ansicpg value, retain it.
     * Otherwise, M$ richedit emits a codepage number determined from the
     * charset of the default font here. Anyway, this value is not used by
     * the reader... */
173 174 175
    nCodePage = GetACP();
  }
  if (nCodePage == CP_UTF8)
176
    success = ME_StreamOutPrint(pStream, "{\\urtf");
177
  else
178
    success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
179 180 181 182

  if (!success)
    return FALSE;

183
  pStream->nDefaultCodePage = nCodePage;
184 185 186 187
  
  /* FIXME: This should be a document property */
  /* TODO: handle SFF_PLAINRTF */
  language = GetUserDefaultLangID(); 
188
  if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
189 190 191
    return FALSE;

  /* FIXME: This should be a document property */
192
  pStream->nDefaultFont = 0;
193 194 195 196 197 198

  return TRUE;
}


static BOOL
199
ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun, const ME_DisplayItem *pLastRun)
200 201
{
  ME_DisplayItem *item = pFirstRun;
202
  ME_FontTableItem *table = pStream->fonttbl;
203 204 205 206 207 208 209 210
  int i;
  
  do {
    CHARFORMAT2W *fmt = &item->member.run.style->fmt;
    COLORREF crColor;

    if (fmt->dwMask & CFM_FACE) {
      WCHAR *face = fmt->szFaceName;
211
      BYTE bCharSet = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
212
  
213
      for (i = 0; i < pStream->nFontTblLen; i++)
214 215 216
        if (table[i].bCharSet == bCharSet
            && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
          break;
217
      if (i == pStream->nFontTblLen) {
218 219
        table[i].bCharSet = bCharSet;
        table[i].szFaceName = face;
220
        pStream->nFontTblLen++;
221 222 223 224 225
      }
    }
    
    if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) {
      crColor = fmt->crTextColor;
226 227
      for (i = 1; i < pStream->nColorTblLen; i++)
        if (pStream->colortbl[i] == crColor)
228
          break;
229 230 231
      if (i == pStream->nColorTblLen) {
        pStream->colortbl[i] = crColor;
        pStream->nColorTblLen++;
232 233 234 235
      }
    }
    if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
      crColor = fmt->crBackColor;
236 237
      for (i = 1; i < pStream->nColorTblLen; i++)
        if (pStream->colortbl[i] == crColor)
238
          break;
239 240 241
      if (i == pStream->nColorTblLen) {
        pStream->colortbl[i] = crColor;
        pStream->nColorTblLen++;
242 243 244 245 246 247 248 249
      }
    }

    if (item == pLastRun)
      break;
    item = ME_FindItemFwd(item, diRun);
  } while (item);
        
250
  if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
251 252
    return FALSE;
  
253
  for (i = 0; i < pStream->nFontTblLen; i++) {
254
    if (table[i].bCharSet != DEFAULT_CHARSET) {
255
      if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
256 257
        return FALSE;
    } else {
258
      if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
259 260
        return FALSE;
    }
261
    if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
Phil Krylov's avatar
Phil Krylov committed
262
      return FALSE;
263
    if (!ME_StreamOutPrint(pStream, ";}\r\n"))
Phil Krylov's avatar
Phil Krylov committed
264
      return FALSE;
265
  }
266
  if (!ME_StreamOutPrint(pStream, "}"))
267 268 269
    return FALSE;

  /* Output colors table if not empty */
270 271
  if (pStream->nColorTblLen > 1) {
    if (!ME_StreamOutPrint(pStream, "{\\colortbl;"))
272
      return FALSE;
273 274 275 276 277
    for (i = 1; i < pStream->nColorTblLen; i++) {
      if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;",
                             pStream->colortbl[i] & 0xFF,
                             (pStream->colortbl[i] >> 8) & 0xFF,
                             (pStream->colortbl[i] >> 16) & 0xFF))
278 279
        return FALSE;
    }
280
    if (!ME_StreamOutPrint(pStream, "}"))
281 282 283 284 285 286 287
      return FALSE;
  }

  return TRUE;
}

static BOOL
288 289
ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
                          const ME_DisplayItem *para)
290
{
291
  ME_DisplayItem *cell;
Phil Krylov's avatar
Phil Krylov committed
292
  char props[STREAMOUT_BUFFER_SIZE] = "";
293

294 295
  if (!ME_StreamOutPrint(pStream, "\\trowd"))
    return FALSE;
296 297 298 299 300
  if (!editor->bEmulateVersion10) { /* v4.1 */
    assert(para->member.para.nFlags & MEPF_ROWSTART);
    cell = para->member.para.next_para->member.para.pCell;
    assert(cell);
    do {
301
      sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
302 303 304 305 306 307 308
      cell = cell->member.cell.next_cell;
    } while (cell->member.cell.next_cell);
  } else { /* v1.0 - 3.0 */
    PARAFORMAT2 *pFmt = para->member.para.pFmt;
    int i;

    assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
309 310 311 312
    if (pFmt->dxOffset)
      sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
    if (pFmt->dxStartIndent)
      sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
313 314
    for (i = 0; i < pFmt->cTabCount; i++)
    {
315
      sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
316
    }
317
  }
318 319
  if (!ME_StreamOutPrint(pStream, props))
    return FALSE;
320 321 322 323 324
  props[0] = '\0';
  return TRUE;
}

static BOOL
325 326
ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
                         const ME_DisplayItem *para)
327 328 329 330
{
  PARAFORMAT2 *fmt = para->member.para.pFmt;
  char props[STREAMOUT_BUFFER_SIZE] = "";
  int i;
331
  
332
  /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
333
  if (!ME_StreamOutPrint(pStream, "\\pard"))
334
    return FALSE;
335

336 337 338
  if (!editor->bEmulateVersion10) { /* v4.1 */
    if (pStream->nNestingLevel > 0)
      strcat(props, "\\intbl");
339 340
    if (pStream->nNestingLevel > 1)
      sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
341 342 343 344
  } else { /* v1.0 - 3.0 */
    if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
      strcat(props, "\\intbl");
  }
Phil Krylov's avatar
Phil Krylov committed
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
  
  /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
   * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
   * set very different from the documentation.
   * (Tested with RichEdit 5.50.25.0601) */
  
  if (fmt->dwMask & PFM_ALIGNMENT) {
    switch (fmt->wAlignment) {
      case PFA_LEFT:
        /* Default alignment: not emitted */
        break;
      case PFA_RIGHT:
        strcat(props, "\\qr");
        break;
      case PFA_CENTER:
        strcat(props, "\\qc");
        break;
      case PFA_JUSTIFY:
        strcat(props, "\\qj");
        break;
    }
366
  }
Phil Krylov's avatar
Phil Krylov committed
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
  
  if (fmt->dwMask & PFM_LINESPACING) {
    /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
     * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
    switch (fmt->bLineSpacingRule) {
      case 0: /* Single spacing */
        strcat(props, "\\sl-240\\slmult1");
        break;
      case 1: /* 1.5 spacing */
        strcat(props, "\\sl-360\\slmult1");
        break;
      case 2: /* Double spacing */
        strcat(props, "\\sl-480\\slmult1");
        break;
      case 3:
382
        sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
Phil Krylov's avatar
Phil Krylov committed
383 384
        break;
      case 4:
385
        sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
Phil Krylov's avatar
Phil Krylov committed
386 387
        break;
      case 5:
388
        sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
Phil Krylov's avatar
Phil Krylov committed
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
        break;
    }
  }

  if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
    strcat(props, "\\hyph0");
  if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
    strcat(props, "\\keep");
  if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
    strcat(props, "\\keepn");
  if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
    strcat(props, "\\noline");
  if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
    strcat(props, "\\nowidctlpar");
  if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
    strcat(props, "\\pagebb");
  if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
    strcat(props, "\\rtlpar");
  if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
    strcat(props, "\\sbys");
  
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
  if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
        fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
  {
    if (fmt->dwMask & PFM_OFFSET)
      sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
    if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT)
      sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
    if (fmt->dwMask & PFM_RIGHTINDENT)
      sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
    if (fmt->dwMask & PFM_TABSTOPS) {
      static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };

      for (i = 0; i < fmt->cTabCount; i++) {
        switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
          case 1:
            strcat(props, "\\tqc");
            break;
          case 2:
            strcat(props, "\\tqr");
            break;
          case 3:
            strcat(props, "\\tqdec");
            break;
          case 4:
            /* Word bar tab (vertical bar). Handled below */
            break;
        }
        if (fmt->rgxTabs[i] >> 28 <= 5)
          strcat(props, leader[fmt->rgxTabs[i] >> 28]);
        sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
      }
    }
  }
Phil Krylov's avatar
Phil Krylov committed
443
  if (fmt->dwMask & PFM_SPACEAFTER)
444
    sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
Phil Krylov's avatar
Phil Krylov committed
445
  if (fmt->dwMask & PFM_SPACEBEFORE)
446
    sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
Phil Krylov's avatar
Phil Krylov committed
447 448 449 450
  if (fmt->dwMask & PFM_STYLE)
    sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
  
  if (fmt->dwMask & PFM_SHADING) {
451
    static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
Phil Krylov's avatar
Phil Krylov committed
452 453 454 455 456 457 458 459 460 461 462 463
                                     "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
                                     "\\bghoriz", "\\bgvert", "\\bgfdiag",
                                     "\\bgbdiag", "\\bgcross", "\\bgdcross",
                                     "", "", "" };
    if (fmt->wShadingWeight)
      sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
    if (fmt->wShadingStyle & 0xF)
      strcat(props, style[fmt->wShadingStyle & 0xF]);
    sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
            (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
  }
  
464
  if (*props && !ME_StreamOutPrint(pStream, props))
465 466 467 468 469 470 471
    return FALSE;

  return TRUE;
}


static BOOL
472
ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
473 474 475 476 477 478 479 480 481 482
{
  char props[STREAMOUT_BUFFER_SIZE] = "";
  int i;

  if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
    strcat(props, "\\caps");
  if (fmt->dwMask & CFM_ANIMATION)
    sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
  if (fmt->dwMask & CFM_BACKCOLOR) {
    if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
483 484
      for (i = 1; i < pStream->nColorTblLen; i++)
        if (pStream->colortbl[i] == fmt->crBackColor) {
485 486 487 488 489 490 491 492 493
          sprintf(props + strlen(props), "\\cb%u", i);
          break;
        }
    }
  }
  if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
    strcat(props, "\\b");
  if (fmt->dwMask & CFM_COLOR) {
    if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
494 495
      for (i = 1; i < pStream->nColorTblLen; i++)
        if (pStream->colortbl[i] == fmt->crTextColor) {
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
          sprintf(props + strlen(props), "\\cf%u", i);
          break;
        }
    }
  }
  /* TODO: CFM_DISABLED */
  if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
    strcat(props, "\\embo");
  if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
    strcat(props, "\\v");
  if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
    strcat(props, "\\impr");
  if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
    strcat(props, "\\i");
  if (fmt->dwMask & CFM_KERNING)
    sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
  if (fmt->dwMask & CFM_LCID) {
    /* TODO: handle SFF_PLAINRTF */
    if (LOWORD(fmt->lcid) == 1024)
      strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
    else
      sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
  }
  /* CFM_LINK is not streamed out by M$ */
  if (fmt->dwMask & CFM_OFFSET) {
    if (fmt->yOffset >= 0)
522
      sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
523
    else
524
      sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
525 526 527 528 529 530 531 532
  }
  if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
    strcat(props, "\\outl");
  if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
    strcat(props, "\\protect");
  /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
  if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
    strcat(props, "\\shad");
Phil Krylov's avatar
Phil Krylov committed
533
  if (fmt->dwMask & CFM_SIZE)
534
    sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
  if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
    strcat(props, "\\scaps");
  if (fmt->dwMask & CFM_SPACING)
    sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
  if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
    strcat(props, "\\strike");
  if (fmt->dwMask & CFM_STYLE) {
    sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
    /* TODO: emit style contents here */
  }
  if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
    if (fmt->dwEffects & CFE_SUBSCRIPT)
      strcat(props, "\\sub");
    else if (fmt->dwEffects & CFE_SUPERSCRIPT)
      strcat(props, "\\super");
  }
  if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
    if (fmt->dwMask & CFM_UNDERLINETYPE)
      switch (fmt->bUnderlineType) {
        case CFU_CF1UNDERLINE:
        case CFU_UNDERLINE:
          strcat(props, "\\ul");
          break;
        case CFU_UNDERLINEDOTTED:
          strcat(props, "\\uld");
          break;
        case CFU_UNDERLINEDOUBLE:
          strcat(props, "\\uldb");
          break;
        case CFU_UNDERLINEWORD:
          strcat(props, "\\ulw");
          break;
        case CFU_UNDERLINENONE:
        default:
          strcat(props, "\\ul0");
          break;
      }
    else if (fmt->dwEffects & CFE_UNDERLINE)
      strcat(props, "\\ul");
  }
  /* FIXME: How to emit CFM_WEIGHT? */
  
  if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
    WCHAR *szFaceName;
    
    if (fmt->dwMask & CFM_FACE)
      szFaceName = fmt->szFaceName;
    else
583 584 585 586
      szFaceName = pStream->fonttbl[0].szFaceName;
    for (i = 0; i < pStream->nFontTblLen; i++) {
      if (szFaceName == pStream->fonttbl[i].szFaceName
          || !lstrcmpW(szFaceName, pStream->fonttbl[i].szFaceName))
587
        if (!(fmt->dwMask & CFM_CHARSET)
588
            || fmt->bCharSet == pStream->fonttbl[i].bCharSet)
589 590
          break;
    }
591
    if (i < pStream->nFontTblLen)
592
    {
593
      if (i != pStream->nDefaultFont)
594
        sprintf(props + strlen(props), "\\f%u", i);
595 596

      /* In UTF-8 mode, charsets/codepages are not used */
597
      if (pStream->nDefaultCodePage != CP_UTF8)
598
      {
599 600
        if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
          pStream->nCodePage = pStream->nDefaultCodePage;
601
        else
602
          pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
603 604
      }
    }
605 606 607
  }
  if (*props)
    strcat(props, " ");
608
  if (!ME_StreamOutPrint(pStream, props))
609 610 611 612 613 614
    return FALSE;
  return TRUE;
}


static BOOL
615
ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
616 617 618
{
  char buffer[STREAMOUT_BUFFER_SIZE];
  int pos = 0;
619
  int fit, nBytes, i;
Phil Krylov's avatar
Phil Krylov committed
620 621 622 623

  if (nChars == -1)
    nChars = lstrlenW(text);
  
624
  while (nChars) {
625
    /* In UTF-8 mode, font charsets are not used. */
626
    if (pStream->nDefaultCodePage == CP_UTF8) {
627 628
      /* 6 is the maximum character length in UTF-8 */
      fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
629 630
      nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
                                   STREAMOUT_BUFFER_SIZE, NULL, NULL);
631 632
      nChars -= fit;
      text += fit;
633
      for (i = 0; i < nBytes; i++)
634
        if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
635
          if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
636 637 638
            return FALSE;
          pos = i;
        }
639
      if (pos < nBytes)
640
        if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
641 642 643 644 645 646 647 648
          return FALSE;
      pos = 0;
    } else if (*text < 128) {
      if (*text == '{' || *text == '}' || *text == '\\')
        buffer[pos++] = '\\';
      buffer[pos++] = (char)(*text++);
      nChars--;
    } else {
649
      BOOL unknown = FALSE;
650 651
      char letter[3];

652 653 654 655
      /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
       * codepages including CP_SYMBOL for which the last parameter must be set
       * to NULL for the function to succeed. But in Wine we need to care only
       * about CP_SYMBOL */
656
      nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
657
                                   letter, 3, NULL,
658
                                   (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
659
      if (unknown)
660
        pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
661
      else if ((BYTE)*letter < 128) {
662 663 664
        if (*letter == '{' || *letter == '}' || *letter == '\\')
          buffer[pos++] = '\\';
        buffer[pos++] = *letter;
665 666
      } else {
         for (i = 0; i < nBytes; i++)
667
           pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
668
      }
669 670 671
      text++;
      nChars--;
    }
672
    if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
673
      if (!ME_StreamOutMove(pStream, buffer, pos))
674 675 676 677
        return FALSE;
      pos = 0;
    }
  }
678
  return ME_StreamOutMove(pStream, buffer, pos);
679 680 681 682
}


static BOOL
683
ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream, int nStart, int nChars, int dwFormat)
684
{
685 686 687
  ME_DisplayItem *p, *pEnd, *pPara;
  int nOffset, nEndLen; 
  
688 689 690
  ME_RunOfsFromCharOfs(editor, nStart, &p, &nOffset);
  ME_RunOfsFromCharOfs(editor, nStart+nChars, &pEnd, &nEndLen);
  
691 692
  pPara = ME_GetParagraph(p);
  
693
  if (!ME_StreamOutRTFHeader(pStream, dwFormat))
694 695
    return FALSE;

696
  if (!ME_StreamOutRTFFontAndColorTbl(pStream, p, pEnd))
697 698 699 700 701
    return FALSE;
  
  /* TODO: stylesheet table */
  
  /* FIXME: maybe emit something smarter for the generator? */
702
  if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0.????;}"))
703 704 705 706 707 708 709 710 711 712
    return FALSE;

  /* TODO: information group */

  /* TODO: document formatting properties */

  /* FIXME: We have only one document section */

  /* TODO: section formatting properties */

713
  if (!ME_StreamOutRTFParaProps(editor, pStream, ME_GetParagraph(p)))
714 715 716 717 718 719 720
    return FALSE;

  while(1)
  {
    switch(p->type)
    {
      case diParagraph:
721 722 723
        if (!editor->bEmulateVersion10) { /* v4.1 */
          if (p->member.para.nFlags & MEPF_ROWSTART) {
            pStream->nNestingLevel++;
724 725 726 727
            if (pStream->nNestingLevel == 1) {
              if (!ME_StreamOutRTFTableProps(editor, pStream, p))
                return FALSE;
            }
728 729
          } else if (p->member.para.nFlags & MEPF_ROWEND) {
            pStream->nNestingLevel--;
730 731 732 733 734 735 736 737 738 739 740
            if (pStream->nNestingLevel > 1) {
              if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
                return FALSE;
              if (!ME_StreamOutRTFTableProps(editor, pStream, p))
                return FALSE;
              if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
                return FALSE;
            } else {
              if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
                return FALSE;
            }
741 742 743 744 745 746 747 748 749 750 751
          } else if (!ME_StreamOutRTFParaProps(editor, pStream, p)) {
            return FALSE;
          }
        } else { /* v1.0 - 3.0 */
          if (p->member.para.pFmt->dwMask & PFM_TABLE &&
              p->member.para.pFmt->wEffects & PFE_TABLE)
          {
            if (!ME_StreamOutRTFTableProps(editor, pStream, p))
              return FALSE;
          }
          if (!ME_StreamOutRTFParaProps(editor, pStream, p))
752 753
            return FALSE;
        }
754
        pPara = p;
755 756 757 758
        break;
      case diRun:
        if (p == pEnd && !nEndLen)
          break;
759
        TRACE("flags %xh\n", p->member.run.nFlags);
760
        /* TODO: emit embedded objects */
761 762
        if (pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
          break;
763
        if (p->member.run.nFlags & MERF_GRAPHICS) {
764
          FIXME("embedded objects are not handled\n");
765
        } else if (p->member.run.nFlags & MERF_TAB) {
766 767
          if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
              pPara->member.para.pFmt->dwMask & PFM_TABLE &&
768 769 770 771 772 773 774 775
              pPara->member.para.pFmt->wEffects & PFE_TABLE)
          {
            if (!ME_StreamOutPrint(pStream, "\\cell "))
              return FALSE;
          } else {
            if (!ME_StreamOutPrint(pStream, "\\tab "))
              return FALSE;
          }
776
        } else if (p->member.run.nFlags & MERF_ENDCELL) {
777 778 779 780 781 782 783
          if (pStream->nNestingLevel > 1) {
            if (!ME_StreamOutPrint(pStream, "\\nestcell "))
              return FALSE;
          } else {
            if (!ME_StreamOutPrint(pStream, "\\cell "))
              return FALSE;
          }
784
          nChars--;
785
        } else if (p->member.run.nFlags & MERF_ENDPARA) {
786 787 788
          if (pPara->member.para.pFmt->dwMask & PFM_TABLE &&
              pPara->member.para.pFmt->wEffects & PFE_TABLE &&
              !(pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
789
          {
790
            if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
791 792
              return FALSE;
          } else {
793
            if (!ME_StreamOutPrint(pStream, "\r\n\\par"))
794 795
              return FALSE;
          }
796 797 798
          /* Skip as many characters as required by current line break */
          nChars -= (p->member.run.nCR <= nChars) ? p->member.run.nCR : nChars;
          nChars -= (p->member.run.nLF <= nChars) ? p->member.run.nLF : nChars;
799 800 801 802
        } else if (p->member.run.nFlags & MERF_ENDROW) {
          if (!ME_StreamOutPrint(pStream, "\\line \r\n"))
            return FALSE;
          nChars--;
803
        } else {
804 805
          int nEnd;
          
806
          if (!ME_StreamOutPrint(pStream, "{"))
807 808
            return FALSE;
          TRACE("style %p\n", p->member.run.style);
809
          if (!ME_StreamOutRTFCharProps(pStream, &p->member.run.style->fmt))
810 811
            return FALSE;
        
812
          nEnd = (p == pEnd) ? nEndLen : ME_StrLen(p->member.run.strText);
813
          if (!ME_StreamOutRTFText(pStream, p->member.run.strText->szData + nOffset, nEnd - nOffset))
814
            return FALSE;
815
          nOffset = 0;
816
          if (!ME_StreamOutPrint(pStream, "}"))
817 818 819
            return FALSE;
        }
        break;
820 821
      default: /* we missed the last item */
        assert(0);
822
    }
823
    if (p == pEnd)
824
      break;
825
    p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
826
  }
827
  if (!ME_StreamOutPrint(pStream, "}"))
828 829 830 831 832 833
    return FALSE;
  return TRUE;
}


static BOOL
834
ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream, int nStart, int nChars, DWORD dwFormat)
835
{
836
  /* FIXME: use ME_RunOfsFromCharOfs */
837 838 839
  ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart);
  int nLen;
  UINT nCodePage = CP_ACP;
840
  char *buffer = NULL;
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
  int nBufLen = 0;
  BOOL success = TRUE;

  if (!item)
    return FALSE;
   
  if (dwFormat & SF_USECODEPAGE)
    nCodePage = HIWORD(dwFormat);

  /* TODO: Handle SF_TEXTIZED */
  
  while (success && nChars && item) {
    nLen = ME_StrLen(item->member.run.strText) - nStart;
    if (nLen > nChars)
      nLen = nChars;

    if (item->member.run.nFlags & MERF_ENDPARA) {
858
      static const WCHAR szEOL[2] = { '\r', '\n' };
859
      
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
      if (!editor->bEmulateVersion10) {
        /* richedit 2.0 - all line breaks are \r\n */
        if (dwFormat & SF_UNICODE)
          success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
        else
          success = ME_StreamOutMove(pStream, "\r\n", 2);
        assert(nLen == 1);
      } else {
        int i; int tnLen;

        /* richedit 1.0 - need to honor actual \r and \n amounts */
        nLen = item->member.run.nCR + item->member.run.nLF;
        if (nLen > nChars)
          nLen = nChars;
        tnLen = nLen;

        i = 0;
        while (tnLen > 0 && i < item->member.run.nCR) {
          if (dwFormat & SF_UNICODE)
            success = ME_StreamOutMove(pStream, (const char *)(&szEOL[0]), sizeof(WCHAR));
          else
            success = ME_StreamOutMove(pStream, "\r", 1);
          tnLen--; i++;
        }
        i = 0;
        while (tnLen > 0 && i < item->member.run.nLF) {
          if (dwFormat & SF_UNICODE)
            success = ME_StreamOutMove(pStream, (const char *)(&szEOL[1]), sizeof(WCHAR));
          else
            success = ME_StreamOutMove(pStream, "\n", 1);
          tnLen--; i++;
        }
      }
893 894
    } else {
      if (dwFormat & SF_UNICODE)
895
        success = ME_StreamOutMove(pStream, (const char *)(item->member.run.strText->szData + nStart),
896 897 898 899 900 901 902
                                   sizeof(WCHAR) * nLen);
      else {
        int nSize;

        nSize = WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
                                    nLen, NULL, 0, NULL, NULL);
        if (nSize > nBufLen) {
903
          FREE_OBJ(buffer);
904
          buffer = ALLOC_N_OBJ(char, nSize);
905 906 907 908
          nBufLen = nSize;
        }
        WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart,
                            nLen, buffer, nSize, NULL, NULL);
909
        success = ME_StreamOutMove(pStream, buffer, nSize);
910 911 912 913 914 915 916 917
      }
    }
    
    nChars -= nLen;
    nStart = 0;
    item = ME_FindItemFwd(item, diRun);
  }
  
918
  FREE_OBJ(buffer);
919 920 921 922 923
  return success;
}


LRESULT
924
ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat, int nStart, int nTo, EDITSTREAM *stream)
925
{
926
  ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
927 928

  if (nTo == -1)
929
  {
930
    nTo = ME_GetTextLength(editor);
931 932 933 934
    /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
    if (dwFormat & SF_RTF)
      nTo++;
  }
935
  TRACE("from %d to %d\n", nStart, nTo);
936

937
  if (dwFormat & SF_RTF)
938
    ME_StreamOutRTF(editor, pStream, nStart, nTo - nStart, dwFormat);
939
  else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
940 941 942 943
    ME_StreamOutText(editor, pStream, nStart, nTo - nStart, dwFormat);
  if (!pStream->stream->dwError)
    ME_StreamOutFlush(pStream);
  return ME_StreamOutFree(pStream);
944
}
945 946 947 948 949 950 951 952 953 954 955 956 957 958

LRESULT
ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
{
  int nStart, nTo;

  if (dwFormat & SFF_SELECTION)
    ME_GetSelection(editor, &nStart, &nTo);
  else {
    nStart = 0;
    nTo = -1;
  }
  return ME_StreamOutRange(editor, dwFormat, nStart, nTo, stream);
}