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