d3d8/tests: Add a system memory miptree layout test.
[wine.git] / dlls / riched20 / writer.c
blob76cf5d73ce129930c28523d7777462f079299eb2
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 if (nWritten == pStream->pos)
66 pStream->written += nWritten;
67 pStream->pos = 0;
68 return TRUE;
72 static LONG
73 ME_StreamOutFree(ME_OutStream *pStream)
75 LONG written = pStream->written;
76 TRACE("total length = %u\n", written);
78 FREE_OBJ(pStream);
79 return written;
83 static BOOL
84 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
86 while (len) {
87 int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
88 int fit = min(space, len);
90 TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
91 memmove(pStream->buffer + pStream->pos, buffer, fit);
92 len -= fit;
93 buffer += fit;
94 pStream->pos += fit;
95 if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
96 if (!ME_StreamOutFlush(pStream))
97 return FALSE;
100 return TRUE;
104 static BOOL
105 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
107 char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
108 int len;
109 va_list valist;
111 va_start(valist, format);
112 len = vsnprintf(string, sizeof(string), format, valist);
113 va_end(valist);
115 return ME_StreamOutMove(pStream, string, len);
119 static BOOL
120 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
122 const char *cCharSet = NULL;
123 UINT nCodePage;
124 LANGID language;
125 BOOL success;
127 if (dwFormat & SF_USECODEPAGE) {
128 CPINFOEXW info;
130 switch (HIWORD(dwFormat)) {
131 case CP_ACP:
132 cCharSet = "ansi";
133 nCodePage = GetACP();
134 break;
135 case CP_OEMCP:
136 nCodePage = GetOEMCP();
137 if (nCodePage == 437)
138 cCharSet = "pc";
139 else if (nCodePage == 850)
140 cCharSet = "pca";
141 else
142 cCharSet = "ansi";
143 break;
144 case CP_UTF8:
145 nCodePage = CP_UTF8;
146 break;
147 default:
148 if (HIWORD(dwFormat) == CP_MACCP) {
149 cCharSet = "mac";
150 nCodePage = 10000; /* MacRoman */
151 } else {
152 cCharSet = "ansi";
153 nCodePage = 1252; /* Latin-1 */
155 if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
156 nCodePage = info.CodePage;
158 } else {
159 cCharSet = "ansi";
160 /* TODO: If the original document contained an \ansicpg value, retain it.
161 * Otherwise, M$ richedit emits a codepage number determined from the
162 * charset of the default font here. Anyway, this value is not used by
163 * the reader... */
164 nCodePage = GetACP();
166 if (nCodePage == CP_UTF8)
167 success = ME_StreamOutPrint(pStream, "{\\urtf");
168 else
169 success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
171 if (!success)
172 return FALSE;
174 pStream->nDefaultCodePage = nCodePage;
176 /* FIXME: This should be a document property */
177 /* TODO: handle SFF_PLAINRTF */
178 language = GetUserDefaultLangID();
179 if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
180 return FALSE;
182 /* FIXME: This should be a document property */
183 pStream->nDefaultFont = 0;
185 return TRUE;
189 static BOOL
190 ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun,
191 ME_DisplayItem *pLastRun)
193 ME_DisplayItem *item = pFirstRun;
194 ME_FontTableItem *table = pStream->fonttbl;
195 unsigned int i;
196 ME_DisplayItem *pLastPara = ME_GetParagraph(pLastRun);
197 ME_DisplayItem *pCell = NULL;
199 do {
200 CHARFORMAT2W *fmt = &item->member.run.style->fmt;
201 COLORREF crColor;
203 if (fmt->dwMask & CFM_FACE) {
204 WCHAR *face = fmt->szFaceName;
205 BYTE bCharSet = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
207 for (i = 0; i < pStream->nFontTblLen; i++)
208 if (table[i].bCharSet == bCharSet
209 && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
210 break;
211 if (i == pStream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE) {
212 table[i].bCharSet = bCharSet;
213 table[i].szFaceName = face;
214 pStream->nFontTblLen++;
218 if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) {
219 crColor = fmt->crTextColor;
220 for (i = 1; i < pStream->nColorTblLen; i++)
221 if (pStream->colortbl[i] == crColor)
222 break;
223 if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) {
224 pStream->colortbl[i] = crColor;
225 pStream->nColorTblLen++;
228 if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
229 crColor = fmt->crBackColor;
230 for (i = 1; i < pStream->nColorTblLen; i++)
231 if (pStream->colortbl[i] == crColor)
232 break;
233 if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) {
234 pStream->colortbl[i] = crColor;
235 pStream->nColorTblLen++;
239 if (item == pLastRun)
240 break;
241 item = ME_FindItemFwd(item, diRun);
242 } while (item);
243 item = ME_GetParagraph(pFirstRun);
244 do {
245 if ((pCell = item->member.para.pCell))
247 ME_Border* borders[4] = { &pCell->member.cell.border.top,
248 &pCell->member.cell.border.left,
249 &pCell->member.cell.border.bottom,
250 &pCell->member.cell.border.right };
251 for (i = 0; i < 4; i++)
253 if (borders[i]->width > 0)
255 unsigned int j;
256 COLORREF crColor = borders[i]->colorRef;
257 for (j = 1; j < pStream->nColorTblLen; j++)
258 if (pStream->colortbl[j] == crColor)
259 break;
260 if (j == pStream->nColorTblLen && j < STREAMOUT_COLORTBL_SIZE) {
261 pStream->colortbl[j] = crColor;
262 pStream->nColorTblLen++;
267 if (item == pLastPara)
268 break;
269 item = item->member.para.next_para;
270 } while (item);
272 if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
273 return FALSE;
275 for (i = 0; i < pStream->nFontTblLen; i++) {
276 if (table[i].bCharSet != DEFAULT_CHARSET) {
277 if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
278 return FALSE;
279 } else {
280 if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
281 return FALSE;
283 if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
284 return FALSE;
285 if (!ME_StreamOutPrint(pStream, ";}"))
286 return FALSE;
288 if (!ME_StreamOutPrint(pStream, "}\r\n"))
289 return FALSE;
291 /* It seems like Open Office ignores \deff0 tag at RTF-header.
292 As result it can't correctly parse text before first \fN tag,
293 so we can put \f0 immediately after font table. This forces
294 parser to use the same font, that \deff0 specifies.
295 It makes OOffice happy */
296 if (!ME_StreamOutPrint(pStream, "\\f0"))
297 return FALSE;
299 /* Output the color table */
300 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
301 for (i = 1; i < pStream->nColorTblLen; i++)
303 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
304 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
305 return FALSE;
307 if (!ME_StreamOutPrint(pStream, "}")) return FALSE;
309 return TRUE;
312 static BOOL
313 ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
314 ME_DisplayItem *para)
316 ME_DisplayItem *cell;
317 char props[STREAMOUT_BUFFER_SIZE] = "";
318 int i;
319 const char sideChar[4] = {'t','l','b','r'};
321 if (!ME_StreamOutPrint(pStream, "\\trowd"))
322 return FALSE;
323 if (!editor->bEmulateVersion10) { /* v4.1 */
324 PARAFORMAT2 *pFmt = ME_GetTableRowEnd(para)->member.para.pFmt;
325 para = ME_GetTableRowStart(para);
326 cell = para->member.para.next_para->member.para.pCell;
327 assert(cell);
328 if (pFmt->dxOffset)
329 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
330 if (pFmt->dxStartIndent)
331 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
332 do {
333 ME_Border* borders[4] = { &cell->member.cell.border.top,
334 &cell->member.cell.border.left,
335 &cell->member.cell.border.bottom,
336 &cell->member.cell.border.right };
337 for (i = 0; i < 4; i++)
339 if (borders[i]->width)
341 unsigned int j;
342 COLORREF crColor = borders[i]->colorRef;
343 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
344 sprintf(props + strlen(props), "\\brdrs");
345 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
346 for (j = 1; j < pStream->nColorTblLen; j++) {
347 if (pStream->colortbl[j] == crColor) {
348 sprintf(props + strlen(props), "\\brdrcf%u", j);
349 break;
354 sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
355 cell = cell->member.cell.next_cell;
356 } while (cell->member.cell.next_cell);
357 } else { /* v1.0 - 3.0 */
358 const ME_Border* borders[4] = { &para->member.para.border.top,
359 &para->member.para.border.left,
360 &para->member.para.border.bottom,
361 &para->member.para.border.right };
362 PARAFORMAT2 *pFmt = para->member.para.pFmt;
364 assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
365 if (pFmt->dxOffset)
366 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
367 if (pFmt->dxStartIndent)
368 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
369 for (i = 0; i < 4; i++)
371 if (borders[i]->width)
373 unsigned int j;
374 COLORREF crColor = borders[i]->colorRef;
375 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
376 sprintf(props + strlen(props), "\\brdrs");
377 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
378 for (j = 1; j < pStream->nColorTblLen; j++) {
379 if (pStream->colortbl[j] == crColor) {
380 sprintf(props + strlen(props), "\\brdrcf%u", j);
381 break;
386 for (i = 0; i < pFmt->cTabCount; i++)
388 sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
391 if (!ME_StreamOutPrint(pStream, props))
392 return FALSE;
393 props[0] = '\0';
394 return TRUE;
397 static BOOL
398 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
399 ME_DisplayItem *para)
401 PARAFORMAT2 *fmt = para->member.para.pFmt;
402 char props[STREAMOUT_BUFFER_SIZE] = "";
403 int i;
405 if (!editor->bEmulateVersion10) { /* v4.1 */
406 if (para->member.para.nFlags & MEPF_ROWSTART) {
407 pStream->nNestingLevel++;
408 if (pStream->nNestingLevel == 1) {
409 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
410 return FALSE;
412 return TRUE;
413 } else if (para->member.para.nFlags & MEPF_ROWEND) {
414 pStream->nNestingLevel--;
415 if (pStream->nNestingLevel >= 1) {
416 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
417 return FALSE;
418 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
419 return FALSE;
420 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
421 return FALSE;
422 } else {
423 if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
424 return FALSE;
426 return TRUE;
428 } else { /* v1.0 - 3.0 */
429 if (para->member.para.pFmt->dwMask & PFM_TABLE &&
430 para->member.para.pFmt->wEffects & PFE_TABLE)
432 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
433 return FALSE;
437 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
438 if (!ME_StreamOutPrint(pStream, "\\pard"))
439 return FALSE;
441 if (!editor->bEmulateVersion10) { /* v4.1 */
442 if (pStream->nNestingLevel > 0)
443 strcat(props, "\\intbl");
444 if (pStream->nNestingLevel > 1)
445 sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
446 } else { /* v1.0 - 3.0 */
447 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
448 strcat(props, "\\intbl");
451 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
452 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
453 * set very different from the documentation.
454 * (Tested with RichEdit 5.50.25.0601) */
456 if (fmt->dwMask & PFM_ALIGNMENT) {
457 switch (fmt->wAlignment) {
458 case PFA_LEFT:
459 /* Default alignment: not emitted */
460 break;
461 case PFA_RIGHT:
462 strcat(props, "\\qr");
463 break;
464 case PFA_CENTER:
465 strcat(props, "\\qc");
466 break;
467 case PFA_JUSTIFY:
468 strcat(props, "\\qj");
469 break;
473 if (fmt->dwMask & PFM_LINESPACING) {
474 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
475 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
476 switch (fmt->bLineSpacingRule) {
477 case 0: /* Single spacing */
478 strcat(props, "\\sl-240\\slmult1");
479 break;
480 case 1: /* 1.5 spacing */
481 strcat(props, "\\sl-360\\slmult1");
482 break;
483 case 2: /* Double spacing */
484 strcat(props, "\\sl-480\\slmult1");
485 break;
486 case 3:
487 sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
488 break;
489 case 4:
490 sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
491 break;
492 case 5:
493 sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
494 break;
498 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
499 strcat(props, "\\hyph0");
500 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
501 strcat(props, "\\keep");
502 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
503 strcat(props, "\\keepn");
504 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
505 strcat(props, "\\noline");
506 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
507 strcat(props, "\\nowidctlpar");
508 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
509 strcat(props, "\\pagebb");
510 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
511 strcat(props, "\\rtlpar");
512 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
513 strcat(props, "\\sbys");
515 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
516 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
518 if (fmt->dwMask & PFM_OFFSET)
519 sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
520 if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT)
521 sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
522 if (fmt->dwMask & PFM_RIGHTINDENT)
523 sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
524 if (fmt->dwMask & PFM_TABSTOPS) {
525 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
527 for (i = 0; i < fmt->cTabCount; i++) {
528 switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
529 case 1:
530 strcat(props, "\\tqc");
531 break;
532 case 2:
533 strcat(props, "\\tqr");
534 break;
535 case 3:
536 strcat(props, "\\tqdec");
537 break;
538 case 4:
539 /* Word bar tab (vertical bar). Handled below */
540 break;
542 if (fmt->rgxTabs[i] >> 28 <= 5)
543 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
544 sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
548 if (fmt->dwMask & PFM_SPACEAFTER)
549 sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
550 if (fmt->dwMask & PFM_SPACEBEFORE)
551 sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
552 if (fmt->dwMask & PFM_STYLE)
553 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
555 if (fmt->dwMask & PFM_SHADING) {
556 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
557 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
558 "\\bghoriz", "\\bgvert", "\\bgfdiag",
559 "\\bgbdiag", "\\bgcross", "\\bgdcross",
560 "", "", "" };
561 if (fmt->wShadingWeight)
562 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
563 if (fmt->wShadingStyle & 0xF)
564 strcat(props, style[fmt->wShadingStyle & 0xF]);
565 sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
566 (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
569 if (*props && !ME_StreamOutPrint(pStream, props))
570 return FALSE;
572 return TRUE;
576 static BOOL
577 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
579 char props[STREAMOUT_BUFFER_SIZE] = "";
580 unsigned int i;
582 if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS)
583 strcat(props, "\\caps");
584 if (fmt->dwMask & CFM_ANIMATION)
585 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
586 if (fmt->dwMask & CFM_BACKCOLOR) {
587 if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) {
588 for (i = 1; i < pStream->nColorTblLen; i++)
589 if (pStream->colortbl[i] == fmt->crBackColor) {
590 sprintf(props + strlen(props), "\\cb%u", i);
591 break;
595 if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD)
596 strcat(props, "\\b");
597 if (fmt->dwMask & CFM_COLOR) {
598 if (!(fmt->dwEffects & CFE_AUTOCOLOR)) {
599 for (i = 1; i < pStream->nColorTblLen; i++)
600 if (pStream->colortbl[i] == fmt->crTextColor) {
601 sprintf(props + strlen(props), "\\cf%u", i);
602 break;
606 /* TODO: CFM_DISABLED */
607 if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS)
608 strcat(props, "\\embo");
609 if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN)
610 strcat(props, "\\v");
611 if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT)
612 strcat(props, "\\impr");
613 if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC)
614 strcat(props, "\\i");
615 if (fmt->dwMask & CFM_KERNING)
616 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
617 if (fmt->dwMask & CFM_LCID) {
618 /* TODO: handle SFF_PLAINRTF */
619 if (LOWORD(fmt->lcid) == 1024)
620 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
621 else
622 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
624 /* CFM_LINK is not streamed out by M$ */
625 if (fmt->dwMask & CFM_OFFSET) {
626 if (fmt->yOffset >= 0)
627 sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
628 else
629 sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
631 if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE)
632 strcat(props, "\\outl");
633 if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED)
634 strcat(props, "\\protect");
635 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
636 if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW)
637 strcat(props, "\\shad");
638 if (fmt->dwMask & CFM_SIZE)
639 sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
640 if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS)
641 strcat(props, "\\scaps");
642 if (fmt->dwMask & CFM_SPACING)
643 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
644 if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT)
645 strcat(props, "\\strike");
646 if (fmt->dwMask & CFM_STYLE) {
647 sprintf(props + strlen(props), "\\cs%u", fmt->sStyle);
648 /* TODO: emit style contents here */
650 if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) {
651 if (fmt->dwEffects & CFE_SUBSCRIPT)
652 strcat(props, "\\sub");
653 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
654 strcat(props, "\\super");
656 if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) {
657 if (fmt->dwMask & CFM_UNDERLINETYPE)
658 switch (fmt->bUnderlineType) {
659 case CFU_CF1UNDERLINE:
660 case CFU_UNDERLINE:
661 strcat(props, "\\ul");
662 break;
663 case CFU_UNDERLINEDOTTED:
664 strcat(props, "\\uld");
665 break;
666 case CFU_UNDERLINEDOUBLE:
667 strcat(props, "\\uldb");
668 break;
669 case CFU_UNDERLINEWORD:
670 strcat(props, "\\ulw");
671 break;
672 case CFU_UNDERLINENONE:
673 default:
674 strcat(props, "\\ulnone");
675 break;
677 else if (fmt->dwEffects & CFE_UNDERLINE)
678 strcat(props, "\\ul");
680 /* FIXME: How to emit CFM_WEIGHT? */
682 if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) {
683 WCHAR *szFaceName;
685 if (fmt->dwMask & CFM_FACE)
686 szFaceName = fmt->szFaceName;
687 else
688 szFaceName = pStream->fonttbl[0].szFaceName;
689 for (i = 0; i < pStream->nFontTblLen; i++) {
690 if (szFaceName == pStream->fonttbl[i].szFaceName
691 || !lstrcmpW(szFaceName, pStream->fonttbl[i].szFaceName))
692 if (!(fmt->dwMask & CFM_CHARSET)
693 || fmt->bCharSet == pStream->fonttbl[i].bCharSet)
694 break;
696 if (i < pStream->nFontTblLen)
698 if (i != pStream->nDefaultFont)
699 sprintf(props + strlen(props), "\\f%u", i);
701 /* In UTF-8 mode, charsets/codepages are not used */
702 if (pStream->nDefaultCodePage != CP_UTF8)
704 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
705 pStream->nCodePage = pStream->nDefaultCodePage;
706 else
707 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
711 if (*props)
712 strcat(props, " ");
713 if (!ME_StreamOutPrint(pStream, props))
714 return FALSE;
715 return TRUE;
719 static BOOL
720 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
722 char buffer[STREAMOUT_BUFFER_SIZE];
723 int pos = 0;
724 int fit, nBytes, i;
726 if (nChars == -1)
727 nChars = lstrlenW(text);
729 while (nChars) {
730 /* In UTF-8 mode, font charsets are not used. */
731 if (pStream->nDefaultCodePage == CP_UTF8) {
732 /* 6 is the maximum character length in UTF-8 */
733 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
734 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
735 STREAMOUT_BUFFER_SIZE, NULL, NULL);
736 nChars -= fit;
737 text += fit;
738 for (i = 0; i < nBytes; i++)
739 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
740 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
741 return FALSE;
742 pos = i;
744 if (pos < nBytes)
745 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
746 return FALSE;
747 pos = 0;
748 } else if (*text < 128) {
749 if (*text == '{' || *text == '}' || *text == '\\')
750 buffer[pos++] = '\\';
751 buffer[pos++] = (char)(*text++);
752 nChars--;
753 } else {
754 BOOL unknown = FALSE;
755 char letter[3];
757 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
758 * codepages including CP_SYMBOL for which the last parameter must be set
759 * to NULL for the function to succeed. But in Wine we need to care only
760 * about CP_SYMBOL */
761 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
762 letter, 3, NULL,
763 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
764 if (unknown)
765 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
766 else if ((BYTE)*letter < 128) {
767 if (*letter == '{' || *letter == '}' || *letter == '\\')
768 buffer[pos++] = '\\';
769 buffer[pos++] = *letter;
770 } else {
771 for (i = 0; i < nBytes; i++)
772 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
774 text++;
775 nChars--;
777 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
778 if (!ME_StreamOutMove(pStream, buffer, pos))
779 return FALSE;
780 pos = 0;
783 return ME_StreamOutMove(pStream, buffer, pos);
787 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
788 const ME_Cursor *start, int nChars, int dwFormat)
790 ME_Cursor cursor = *start;
791 ME_DisplayItem *prev_para = cursor.pPara;
792 ME_Cursor endCur = cursor;
793 int actual_chars;
795 actual_chars = ME_MoveCursorChars(editor, &endCur, nChars);
796 /* Include the final \r which MoveCursorChars will ignore. */
797 if (actual_chars != nChars) endCur.nOffset++;
799 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
800 return FALSE;
802 if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun))
803 return FALSE;
805 /* TODO: stylesheet table */
807 /* FIXME: maybe emit something smarter for the generator? */
808 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0.????;}"))
809 return FALSE;
811 /* TODO: information group */
813 /* TODO: document formatting properties */
815 /* FIXME: We have only one document section */
817 /* TODO: section formatting properties */
819 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
820 return FALSE;
822 do {
823 if (cursor.pPara != prev_para)
825 prev_para = cursor.pPara;
826 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
827 return FALSE;
830 if (cursor.pRun == endCur.pRun && !endCur.nOffset)
831 break;
832 TRACE("flags %xh\n", cursor.pRun->member.run.nFlags);
833 /* TODO: emit embedded objects */
834 if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
835 continue;
836 if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) {
837 FIXME("embedded objects are not handled\n");
838 } else if (cursor.pRun->member.run.nFlags & MERF_TAB) {
839 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
840 cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE &&
841 cursor.pPara->member.para.pFmt->wEffects & PFE_TABLE)
843 if (!ME_StreamOutPrint(pStream, "\\cell "))
844 return FALSE;
845 } else {
846 if (!ME_StreamOutPrint(pStream, "\\tab "))
847 return FALSE;
849 } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) {
850 if (pStream->nNestingLevel > 1) {
851 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
852 return FALSE;
853 } else {
854 if (!ME_StreamOutPrint(pStream, "\\cell "))
855 return FALSE;
857 nChars--;
858 } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) {
859 if (cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE &&
860 cursor.pPara->member.para.pFmt->wEffects & PFE_TABLE &&
861 !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
863 if (!ME_StreamOutPrint(pStream, "\\row \r\n"))
864 return FALSE;
865 } else {
866 if (!ME_StreamOutPrint(pStream, "\r\n\\par"))
867 return FALSE;
869 /* Skip as many characters as required by current line break */
870 nChars = max(0, nChars - cursor.pRun->member.run.len);
871 } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) {
872 if (!ME_StreamOutPrint(pStream, "\\line \r\n"))
873 return FALSE;
874 nChars--;
875 } else {
876 int nEnd;
878 if (!ME_StreamOutPrint(pStream, "{"))
879 return FALSE;
880 TRACE("style %p\n", cursor.pRun->member.run.style);
881 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
882 return FALSE;
884 nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len;
885 if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ),
886 nEnd - cursor.nOffset))
887 return FALSE;
888 cursor.nOffset = 0;
889 if (!ME_StreamOutPrint(pStream, "}"))
890 return FALSE;
892 } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun));
894 if (!ME_StreamOutMove(pStream, "}\0", 2))
895 return FALSE;
896 return TRUE;
900 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
901 const ME_Cursor *start, int nChars, DWORD dwFormat)
903 ME_Cursor cursor = *start;
904 int nLen;
905 UINT nCodePage = CP_ACP;
906 char *buffer = NULL;
907 int nBufLen = 0;
908 BOOL success = TRUE;
910 if (!cursor.pRun)
911 return FALSE;
913 if (dwFormat & SF_USECODEPAGE)
914 nCodePage = HIWORD(dwFormat);
916 /* TODO: Handle SF_TEXTIZED */
918 while (success && nChars && cursor.pRun) {
919 nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset);
921 if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA)
923 static const WCHAR szEOL[] = { '\r', '\n' };
925 /* richedit 2.0 - all line breaks are \r\n */
926 if (dwFormat & SF_UNICODE)
927 success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
928 else
929 success = ME_StreamOutMove(pStream, "\r\n", 2);
930 } else {
931 if (dwFormat & SF_UNICODE)
932 success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )),
933 sizeof(WCHAR) * nLen);
934 else {
935 int nSize;
937 nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
938 nLen, NULL, 0, NULL, NULL);
939 if (nSize > nBufLen) {
940 FREE_OBJ(buffer);
941 buffer = ALLOC_N_OBJ(char, nSize);
942 nBufLen = nSize;
944 WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
945 nLen, buffer, nSize, NULL, NULL);
946 success = ME_StreamOutMove(pStream, buffer, nSize);
950 nChars -= nLen;
951 cursor.nOffset = 0;
952 cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun);
955 FREE_OBJ(buffer);
956 return success;
960 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
961 const ME_Cursor *start,
962 int nChars, EDITSTREAM *stream)
964 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
966 if (dwFormat & SF_RTF)
967 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
968 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
969 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
970 if (!pStream->stream->dwError)
971 ME_StreamOutFlush(pStream);
972 return ME_StreamOutFree(pStream);
975 LRESULT
976 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
978 ME_Cursor start;
979 int nChars;
981 if (dwFormat & SFF_SELECTION) {
982 int nStart, nTo;
983 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
984 nChars = nTo - nStart;
985 } else {
986 ME_SetCursorToStart(editor, &start);
987 nChars = ME_GetTextLength(editor);
988 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
989 if (dwFormat & SF_RTF)
990 nChars++;
992 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);