riched20: Append a final \par when writing the whole buffer.
[wine.git] / dlls / riched20 / writer.c
blob2c7386a51c3ae08a994b47dddd4e6237496477b9
1 /*
2 * RichEdit - RTF writer module
4 * Copyright 2005 by Phil Krylov
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.
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.
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
21 #include "config.h"
22 #include "wine/port.h"
24 #include "editor.h"
25 #include "rtf.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
30 static BOOL
31 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
34 static ME_OutStream*
35 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
37 ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream);
38 pStream->stream = stream;
39 pStream->stream->dwError = 0;
40 pStream->pos = 0;
41 pStream->written = 0;
42 pStream->nFontTblLen = 0;
43 pStream->nColorTblLen = 1;
44 pStream->nNestingLevel = 0;
45 return pStream;
49 static BOOL
50 ME_StreamOutFlush(ME_OutStream *pStream)
52 LONG nWritten = 0;
53 EDITSTREAM *stream = pStream->stream;
55 if (pStream->pos) {
56 TRACE("sending %u bytes\n", pStream->pos);
57 nWritten = pStream->pos;
58 stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer,
59 pStream->pos, &nWritten);
60 TRACE("error=%u written=%u\n", stream->dwError, nWritten);
61 if (nWritten == 0 || stream->dwError)
62 return FALSE;
63 /* Don't resend partial chunks if nWritten < pStream->pos */
65 pStream->pos = 0;
66 return TRUE;
70 static LONG
71 ME_StreamOutFree(ME_OutStream *pStream)
73 LONG written = pStream->written;
74 TRACE("total length = %u\n", written);
76 FREE_OBJ(pStream);
77 return written;
81 static BOOL
82 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
84 while (len) {
85 int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
86 int fit = min(space, len);
88 TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
89 memmove(pStream->buffer + pStream->pos, buffer, fit);
90 len -= fit;
91 buffer += fit;
92 pStream->pos += fit;
93 if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
94 if (!ME_StreamOutFlush(pStream))
95 return FALSE;
98 return TRUE;
102 static BOOL
103 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
105 char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
106 int len;
107 va_list valist;
109 va_start(valist, format);
110 len = vsnprintf(string, sizeof(string), format, valist);
111 va_end(valist);
113 return ME_StreamOutMove(pStream, string, len);
117 static BOOL
118 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
120 const char *cCharSet = NULL;
121 UINT nCodePage;
122 LANGID language;
123 BOOL success;
125 if (dwFormat & SF_USECODEPAGE) {
126 CPINFOEXW info;
128 switch (HIWORD(dwFormat)) {
129 case CP_ACP:
130 cCharSet = "ansi";
131 nCodePage = GetACP();
132 break;
133 case CP_OEMCP:
134 nCodePage = GetOEMCP();
135 if (nCodePage == 437)
136 cCharSet = "pc";
137 else if (nCodePage == 850)
138 cCharSet = "pca";
139 else
140 cCharSet = "ansi";
141 break;
142 case CP_UTF8:
143 nCodePage = CP_UTF8;
144 break;
145 default:
146 if (HIWORD(dwFormat) == CP_MACCP) {
147 cCharSet = "mac";
148 nCodePage = 10000; /* MacRoman */
149 } else {
150 cCharSet = "ansi";
151 nCodePage = 1252; /* Latin-1 */
153 if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
154 nCodePage = info.CodePage;
156 } else {
157 cCharSet = "ansi";
158 /* TODO: If the original document contained an \ansicpg value, retain it.
159 * Otherwise, M$ richedit emits a codepage number determined from the
160 * charset of the default font here. Anyway, this value is not used by
161 * the reader... */
162 nCodePage = GetACP();
164 if (nCodePage == CP_UTF8)
165 success = ME_StreamOutPrint(pStream, "{\\urtf");
166 else
167 success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
169 if (!success)
170 return FALSE;
172 pStream->nDefaultCodePage = nCodePage;
174 /* FIXME: This should be a document property */
175 /* TODO: handle SFF_PLAINRTF */
176 language = GetUserDefaultLangID();
177 if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
178 return FALSE;
180 /* FIXME: This should be a document property */
181 pStream->nDefaultFont = 0;
183 return TRUE;
187 static BOOL
188 ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun,
189 ME_DisplayItem *pLastRun)
191 ME_DisplayItem *item = pFirstRun;
192 ME_FontTableItem *table = pStream->fonttbl;
193 unsigned int i;
194 ME_DisplayItem *pLastPara = ME_GetParagraph(pLastRun);
195 ME_DisplayItem *pCell = NULL;
197 do {
198 CHARFORMAT2W *fmt = &item->member.run.style->fmt;
199 COLORREF crColor;
201 if (fmt->dwMask & CFM_FACE) {
202 WCHAR *face = fmt->szFaceName;
203 BYTE bCharSet = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
205 for (i = 0; i < pStream->nFontTblLen; i++)
206 if (table[i].bCharSet == bCharSet
207 && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
208 break;
209 if (i == pStream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE) {
210 table[i].bCharSet = bCharSet;
211 table[i].szFaceName = face;
212 pStream->nFontTblLen++;
216 if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) {
217 crColor = fmt->crTextColor;
218 for (i = 1; i < pStream->nColorTblLen; i++)
219 if (pStream->colortbl[i] == crColor)
220 break;
221 if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) {
222 pStream->colortbl[i] = crColor;
223 pStream->nColorTblLen++;
226 if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
227 crColor = fmt->crBackColor;
228 for (i = 1; i < pStream->nColorTblLen; i++)
229 if (pStream->colortbl[i] == crColor)
230 break;
231 if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) {
232 pStream->colortbl[i] = crColor;
233 pStream->nColorTblLen++;
237 if (item == pLastRun)
238 break;
239 item = ME_FindItemFwd(item, diRun);
240 } while (item);
241 item = ME_GetParagraph(pFirstRun);
242 do {
243 if ((pCell = item->member.para.pCell))
245 ME_Border* borders[4] = { &pCell->member.cell.border.top,
246 &pCell->member.cell.border.left,
247 &pCell->member.cell.border.bottom,
248 &pCell->member.cell.border.right };
249 for (i = 0; i < 4; i++)
251 if (borders[i]->width > 0)
253 unsigned int j;
254 COLORREF crColor = borders[i]->colorRef;
255 for (j = 1; j < pStream->nColorTblLen; j++)
256 if (pStream->colortbl[j] == crColor)
257 break;
258 if (j == pStream->nColorTblLen && j < STREAMOUT_COLORTBL_SIZE) {
259 pStream->colortbl[j] = crColor;
260 pStream->nColorTblLen++;
265 if (item == pLastPara)
266 break;
267 item = item->member.para.next_para;
268 } while (item);
270 if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
271 return FALSE;
273 for (i = 0; i < pStream->nFontTblLen; i++) {
274 if (table[i].bCharSet != DEFAULT_CHARSET) {
275 if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
276 return FALSE;
277 } else {
278 if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
279 return FALSE;
281 if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
282 return FALSE;
283 if (!ME_StreamOutPrint(pStream, ";}"))
284 return FALSE;
286 if (!ME_StreamOutPrint(pStream, "}\r\n"))
287 return FALSE;
289 /* It seems like Open Office ignores \deff0 tag at RTF-header.
290 As result it can't correctly parse text before first \fN tag,
291 so we can put \f0 immediately after font table. This forces
292 parser to use the same font, that \deff0 specifies.
293 It makes OOffice happy */
294 if (!ME_StreamOutPrint(pStream, "\\f0"))
295 return FALSE;
297 /* Output the color table */
298 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
299 for (i = 1; i < pStream->nColorTblLen; i++)
301 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
302 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
303 return FALSE;
305 if (!ME_StreamOutPrint(pStream, "}")) return FALSE;
307 return TRUE;
310 static BOOL
311 ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
312 ME_DisplayItem *para)
314 ME_DisplayItem *cell;
315 char props[STREAMOUT_BUFFER_SIZE] = "";
316 int i;
317 const char sideChar[4] = {'t','l','b','r'};
319 if (!ME_StreamOutPrint(pStream, "\\trowd"))
320 return FALSE;
321 if (!editor->bEmulateVersion10) { /* v4.1 */
322 PARAFORMAT2 *pFmt = ME_GetTableRowEnd(para)->member.para.pFmt;
323 para = ME_GetTableRowStart(para);
324 cell = para->member.para.next_para->member.para.pCell;
325 assert(cell);
326 if (pFmt->dxOffset)
327 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
328 if (pFmt->dxStartIndent)
329 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
330 do {
331 ME_Border* borders[4] = { &cell->member.cell.border.top,
332 &cell->member.cell.border.left,
333 &cell->member.cell.border.bottom,
334 &cell->member.cell.border.right };
335 for (i = 0; i < 4; i++)
337 if (borders[i]->width)
339 unsigned int j;
340 COLORREF crColor = borders[i]->colorRef;
341 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
342 sprintf(props + strlen(props), "\\brdrs");
343 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
344 for (j = 1; j < pStream->nColorTblLen; j++) {
345 if (pStream->colortbl[j] == crColor) {
346 sprintf(props + strlen(props), "\\brdrcf%u", j);
347 break;
352 sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
353 cell = cell->member.cell.next_cell;
354 } while (cell->member.cell.next_cell);
355 } else { /* v1.0 - 3.0 */
356 const ME_Border* borders[4] = { &para->member.para.border.top,
357 &para->member.para.border.left,
358 &para->member.para.border.bottom,
359 &para->member.para.border.right };
360 PARAFORMAT2 *pFmt = para->member.para.pFmt;
362 assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
363 if (pFmt->dxOffset)
364 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
365 if (pFmt->dxStartIndent)
366 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
367 for (i = 0; i < 4; i++)
369 if (borders[i]->width)
371 unsigned int j;
372 COLORREF crColor = borders[i]->colorRef;
373 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
374 sprintf(props + strlen(props), "\\brdrs");
375 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
376 for (j = 1; j < pStream->nColorTblLen; j++) {
377 if (pStream->colortbl[j] == crColor) {
378 sprintf(props + strlen(props), "\\brdrcf%u", j);
379 break;
384 for (i = 0; i < pFmt->cTabCount; i++)
386 sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
389 if (!ME_StreamOutPrint(pStream, props))
390 return FALSE;
391 props[0] = '\0';
392 return TRUE;
395 static BOOL
396 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
397 ME_DisplayItem *para)
399 PARAFORMAT2 *fmt = para->member.para.pFmt;
400 char props[STREAMOUT_BUFFER_SIZE] = "";
401 int i;
403 if (!editor->bEmulateVersion10) { /* v4.1 */
404 if (para->member.para.nFlags & MEPF_ROWSTART) {
405 pStream->nNestingLevel++;
406 if (pStream->nNestingLevel == 1) {
407 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
408 return FALSE;
410 return TRUE;
411 } else if (para->member.para.nFlags & MEPF_ROWEND) {
412 pStream->nNestingLevel--;
413 if (pStream->nNestingLevel >= 1) {
414 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
415 return FALSE;
416 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
417 return FALSE;
418 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
419 return FALSE;
420 } else {
421 if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
422 return FALSE;
424 return TRUE;
426 } else { /* v1.0 - 3.0 */
427 if (para->member.para.pFmt->dwMask & PFM_TABLE &&
428 para->member.para.pFmt->wEffects & PFE_TABLE)
430 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
431 return FALSE;
435 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
436 if (!ME_StreamOutPrint(pStream, "\\pard"))
437 return FALSE;
439 if (!editor->bEmulateVersion10) { /* v4.1 */
440 if (pStream->nNestingLevel > 0)
441 strcat(props, "\\intbl");
442 if (pStream->nNestingLevel > 1)
443 sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
444 } else { /* v1.0 - 3.0 */
445 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
446 strcat(props, "\\intbl");
449 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
450 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
451 * set very different from the documentation.
452 * (Tested with RichEdit 5.50.25.0601) */
454 if (fmt->dwMask & PFM_ALIGNMENT) {
455 switch (fmt->wAlignment) {
456 case PFA_LEFT:
457 /* Default alignment: not emitted */
458 break;
459 case PFA_RIGHT:
460 strcat(props, "\\qr");
461 break;
462 case PFA_CENTER:
463 strcat(props, "\\qc");
464 break;
465 case PFA_JUSTIFY:
466 strcat(props, "\\qj");
467 break;
471 if (fmt->dwMask & PFM_LINESPACING) {
472 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
473 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
474 switch (fmt->bLineSpacingRule) {
475 case 0: /* Single spacing */
476 strcat(props, "\\sl-240\\slmult1");
477 break;
478 case 1: /* 1.5 spacing */
479 strcat(props, "\\sl-360\\slmult1");
480 break;
481 case 2: /* Double spacing */
482 strcat(props, "\\sl-480\\slmult1");
483 break;
484 case 3:
485 sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
486 break;
487 case 4:
488 sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
489 break;
490 case 5:
491 sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
492 break;
496 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
497 strcat(props, "\\hyph0");
498 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
499 strcat(props, "\\keep");
500 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
501 strcat(props, "\\keepn");
502 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
503 strcat(props, "\\noline");
504 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
505 strcat(props, "\\nowidctlpar");
506 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
507 strcat(props, "\\pagebb");
508 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
509 strcat(props, "\\rtlpar");
510 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
511 strcat(props, "\\sbys");
513 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
514 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
516 if (fmt->dwMask & PFM_OFFSET)
517 sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
518 if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT)
519 sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
520 if (fmt->dwMask & PFM_RIGHTINDENT)
521 sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
522 if (fmt->dwMask & PFM_TABSTOPS) {
523 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
525 for (i = 0; i < fmt->cTabCount; i++) {
526 switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
527 case 1:
528 strcat(props, "\\tqc");
529 break;
530 case 2:
531 strcat(props, "\\tqr");
532 break;
533 case 3:
534 strcat(props, "\\tqdec");
535 break;
536 case 4:
537 /* Word bar tab (vertical bar). Handled below */
538 break;
540 if (fmt->rgxTabs[i] >> 28 <= 5)
541 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
542 sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
546 if (fmt->dwMask & PFM_SPACEAFTER)
547 sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
548 if (fmt->dwMask & PFM_SPACEBEFORE)
549 sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
550 if (fmt->dwMask & PFM_STYLE)
551 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
553 if (fmt->dwMask & PFM_SHADING) {
554 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
555 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
556 "\\bghoriz", "\\bgvert", "\\bgfdiag",
557 "\\bgbdiag", "\\bgcross", "\\bgdcross",
558 "", "", "" };
559 if (fmt->wShadingWeight)
560 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
561 if (fmt->wShadingStyle & 0xF)
562 strcat(props, style[fmt->wShadingStyle & 0xF]);
563 sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
564 (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
567 if (*props && !ME_StreamOutPrint(pStream, props))
568 return FALSE;
570 return TRUE;
574 static BOOL
575 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
577 char props[STREAMOUT_BUFFER_SIZE] = "";
578 unsigned int i;
580 if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
581 strcat(props, "\\caps");
582 if (fmt->dwMask & CFM_ANIMATION)
583 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
584 if (fmt->dwMask & CFM_BACKCOLOR) {
585 if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
586 for (i = 1; i < pStream->nColorTblLen; i++)
587 if (pStream->colortbl[i] == fmt->crBackColor) {
588 sprintf(props + strlen(props), "\\cb%u", i);
589 break;
593 if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
594 strcat(props, "\\b");
595 if (fmt->dwMask & CFM_COLOR) {
596 if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
597 for (i = 1; i < pStream->nColorTblLen; i++)
598 if (pStream->colortbl[i] == fmt->crTextColor) {
599 sprintf(props + strlen(props), "\\cf%u", i);
600 break;
604 /* TODO: CFM_DISABLED */
605 if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
606 strcat(props, "\\embo");
607 if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
608 strcat(props, "\\v");
609 if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
610 strcat(props, "\\impr");
611 if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
612 strcat(props, "\\i");
613 if (fmt->dwMask & CFM_KERNING)
614 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
615 if (fmt->dwMask & CFM_LCID) {
616 /* TODO: handle SFF_PLAINRTF */
617 if (LOWORD(fmt->lcid) == 1024)
618 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
619 else
620 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
622 /* CFM_LINK is not streamed out by M$ */
623 if (fmt->dwMask & CFM_OFFSET) {
624 if (fmt->yOffset >= 0)
625 sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
626 else
627 sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
629 if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
630 strcat(props, "\\outl");
631 if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
632 strcat(props, "\\protect");
633 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
634 if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
635 strcat(props, "\\shad");
636 if (fmt->dwMask & CFM_SIZE)
637 sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
638 if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
639 strcat(props, "\\scaps");
640 if (fmt->dwMask & CFM_SPACING)
641 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
642 if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
643 strcat(props, "\\strike");
644 if (fmt->dwMask & CFM_STYLE) {
645 sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
646 /* TODO: emit style contents here */
648 if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
649 if (fmt->dwEffects & CFE_SUBSCRIPT)
650 strcat(props, "\\sub");
651 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
652 strcat(props, "\\super");
654 if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
655 if (fmt->dwMask & CFM_UNDERLINETYPE)
656 switch (fmt->bUnderlineType) {
657 case CFU_CF1UNDERLINE:
658 case CFU_UNDERLINE:
659 strcat(props, "\\ul");
660 break;
661 case CFU_UNDERLINEDOTTED:
662 strcat(props, "\\uld");
663 break;
664 case CFU_UNDERLINEDOUBLE:
665 strcat(props, "\\uldb");
666 break;
667 case CFU_UNDERLINEWORD:
668 strcat(props, "\\ulw");
669 break;
670 case CFU_UNDERLINENONE:
671 default:
672 strcat(props, "\\ulnone");
673 break;
675 else if (fmt->dwEffects & CFE_UNDERLINE)
676 strcat(props, "\\ul");
678 /* FIXME: How to emit CFM_WEIGHT? */
680 if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
681 WCHAR *szFaceName;
683 if (fmt->dwMask & CFM_FACE)
684 szFaceName = fmt->szFaceName;
685 else
686 szFaceName = pStream->fonttbl[0].szFaceName;
687 for (i = 0; i < pStream->nFontTblLen; i++) {
688 if (szFaceName == pStream->fonttbl[i].szFaceName
689 || !lstrcmpW(szFaceName, pStream->fonttbl[i].szFaceName))
690 if (!(fmt->dwMask & CFM_CHARSET)
691 || fmt->bCharSet == pStream->fonttbl[i].bCharSet)
692 break;
694 if (i < pStream->nFontTblLen)
696 if (i != pStream->nDefaultFont)
697 sprintf(props + strlen(props), "\\f%u", i);
699 /* In UTF-8 mode, charsets/codepages are not used */
700 if (pStream->nDefaultCodePage != CP_UTF8)
702 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
703 pStream->nCodePage = pStream->nDefaultCodePage;
704 else
705 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
709 if (*props)
710 strcat(props, " ");
711 if (!ME_StreamOutPrint(pStream, props))
712 return FALSE;
713 return TRUE;
717 static BOOL
718 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
720 char buffer[STREAMOUT_BUFFER_SIZE];
721 int pos = 0;
722 int fit, nBytes, i;
724 if (nChars == -1)
725 nChars = lstrlenW(text);
727 while (nChars) {
728 /* In UTF-8 mode, font charsets are not used. */
729 if (pStream->nDefaultCodePage == CP_UTF8) {
730 /* 6 is the maximum character length in UTF-8 */
731 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
732 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
733 STREAMOUT_BUFFER_SIZE, NULL, NULL);
734 nChars -= fit;
735 text += fit;
736 for (i = 0; i < nBytes; i++)
737 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
738 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
739 return FALSE;
740 pos = i;
742 if (pos < nBytes)
743 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
744 return FALSE;
745 pos = 0;
746 } else if (*text < 128) {
747 if (*text == '{' || *text == '}' || *text == '\\')
748 buffer[pos++] = '\\';
749 buffer[pos++] = (char)(*text++);
750 nChars--;
751 } else {
752 BOOL unknown = FALSE;
753 char letter[3];
755 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
756 * codepages including CP_SYMBOL for which the last parameter must be set
757 * to NULL for the function to succeed. But in Wine we need to care only
758 * about CP_SYMBOL */
759 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
760 letter, 3, NULL,
761 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
762 if (unknown)
763 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
764 else if ((BYTE)*letter < 128) {
765 if (*letter == '{' || *letter == '}' || *letter == '\\')
766 buffer[pos++] = '\\';
767 buffer[pos++] = *letter;
768 } else {
769 for (i = 0; i < nBytes; i++)
770 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
772 text++;
773 nChars--;
775 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
776 if (!ME_StreamOutMove(pStream, buffer, pos))
777 return FALSE;
778 pos = 0;
781 return ME_StreamOutMove(pStream, buffer, pos);
785 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
786 const ME_Cursor *start, int nChars, int dwFormat)
788 ME_Cursor cursor = *start;
789 ME_DisplayItem *prev_para = cursor.pPara;
790 ME_Cursor endCur = cursor;
791 int actual_chars;
793 actual_chars = ME_MoveCursorChars(editor, &endCur, nChars);
794 /* Include the final \r which MoveCursorChars will ignore. */
795 if (actual_chars != nChars) endCur.nOffset++;
797 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
798 return FALSE;
800 if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun))
801 return FALSE;
803 /* TODO: stylesheet table */
805 /* FIXME: maybe emit something smarter for the generator? */
806 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0.????;}"))
807 return FALSE;
809 /* TODO: information group */
811 /* TODO: document formatting properties */
813 /* FIXME: We have only one document section */
815 /* TODO: section formatting properties */
817 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
818 return FALSE;
820 do {
821 if (cursor.pPara != prev_para)
823 prev_para = cursor.pPara;
824 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
825 return FALSE;
828 if (cursor.pRun == endCur.pRun && !endCur.nOffset)
829 break;
830 TRACE("flags %xh\n", cursor.pRun->member.run.nFlags);
831 /* TODO: emit embedded objects */
832 if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
833 continue;
834 if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) {
835 FIXME("embedded objects are not handled\n");
836 } else if (cursor.pRun->member.run.nFlags & MERF_TAB) {
837 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
838 cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE &&
839 cursor.pPara->member.para.pFmt->wEffects & PFE_TABLE)
841 if (!ME_StreamOutPrint(pStream, "\\cell "))
842 return FALSE;
843 } else {
844 if (!ME_StreamOutPrint(pStream, "\\tab "))
845 return FALSE;
847 } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) {
848 if (pStream->nNestingLevel > 1) {
849 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
850 return FALSE;
851 } else {
852 if (!ME_StreamOutPrint(pStream, "\\cell "))
853 return FALSE;
855 nChars--;
856 } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) {
857 if (cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE &&
858 cursor.pPara->member.para.pFmt->wEffects & PFE_TABLE &&
859 !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
861 if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
862 return FALSE;
863 } else {
864 if (!ME_StreamOutPrint(pStream, "\r\n\\par"))
865 return FALSE;
867 /* Skip as many characters as required by current line break */
868 nChars = max(0, nChars - cursor.pRun->member.run.len);
869 } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) {
870 if (!ME_StreamOutPrint(pStream, "\\line \r\n"))
871 return FALSE;
872 nChars--;
873 } else {
874 int nEnd;
876 if (!ME_StreamOutPrint(pStream, "{"))
877 return FALSE;
878 TRACE("style %p\n", cursor.pRun->member.run.style);
879 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
880 return FALSE;
882 nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len;
883 if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ),
884 nEnd - cursor.nOffset))
885 return FALSE;
886 cursor.nOffset = 0;
887 if (!ME_StreamOutPrint(pStream, "}"))
888 return FALSE;
890 } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun));
892 if (!ME_StreamOutMove(pStream, "}\0", 2))
893 return FALSE;
894 return TRUE;
898 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
899 const ME_Cursor *start, int nChars, DWORD dwFormat)
901 ME_Cursor cursor = *start;
902 int nLen;
903 UINT nCodePage = CP_ACP;
904 char *buffer = NULL;
905 int nBufLen = 0;
906 BOOL success = TRUE;
908 if (!cursor.pRun)
909 return FALSE;
911 if (dwFormat & SF_USECODEPAGE)
912 nCodePage = HIWORD(dwFormat);
914 /* TODO: Handle SF_TEXTIZED */
916 while (success && nChars && cursor.pRun) {
917 nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset);
919 if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA)
921 static const WCHAR szEOL[] = { '\r', '\n' };
923 /* richedit 2.0 - all line breaks are \r\n */
924 if (dwFormat & SF_UNICODE)
925 success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
926 else
927 success = ME_StreamOutMove(pStream, "\r\n", 2);
928 } else {
929 if (dwFormat & SF_UNICODE)
930 success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )),
931 sizeof(WCHAR) * nLen);
932 else {
933 int nSize;
935 nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
936 nLen, NULL, 0, NULL, NULL);
937 if (nSize > nBufLen) {
938 FREE_OBJ(buffer);
939 buffer = ALLOC_N_OBJ(char, nSize);
940 nBufLen = nSize;
942 WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
943 nLen, buffer, nSize, NULL, NULL);
944 success = ME_StreamOutMove(pStream, buffer, nSize);
948 nChars -= nLen;
949 cursor.nOffset = 0;
950 cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun);
953 FREE_OBJ(buffer);
954 return success;
958 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
959 const ME_Cursor *start,
960 int nChars, EDITSTREAM *stream)
962 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
964 if (dwFormat & SF_RTF)
965 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
966 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
967 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
968 if (!pStream->stream->dwError)
969 ME_StreamOutFlush(pStream);
970 return ME_StreamOutFree(pStream);
973 LRESULT
974 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
976 ME_Cursor start;
977 int nChars;
979 if (dwFormat & SFF_SELECTION) {
980 int nStart, nTo;
981 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
982 nChars = nTo - nStart;
983 } else {
984 ME_SetCursorToStart(editor, &start);
985 nChars = ME_GetTextLength(editor);
986 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
987 if (dwFormat & SF_RTF)
988 nChars++;
990 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);