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