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