mshtml: Removed no longer used attr_name from event_info_t.
[wine.git] / dlls / riched20 / writer.c
blobbaa288615641adb537b931bbf22df18c11fcc2f7
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 #define NONAMELESSUNION
26 #include "editor.h"
27 #include "rtf.h"
29 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
31 #define STREAMOUT_BUFFER_SIZE 4096
32 #define STREAMOUT_FONTTBL_SIZE 8192
33 #define STREAMOUT_COLORTBL_SIZE 1024
35 typedef struct tagME_OutStream
37 EDITSTREAM *stream;
38 char buffer[STREAMOUT_BUFFER_SIZE];
39 UINT pos, written;
40 UINT nCodePage;
41 UINT nFontTblLen;
42 ME_FontTableItem fonttbl[STREAMOUT_FONTTBL_SIZE];
43 UINT nColorTblLen;
44 COLORREF colortbl[STREAMOUT_COLORTBL_SIZE];
45 UINT nDefaultFont;
46 UINT nDefaultCodePage;
47 /* nNestingLevel = 0 means we aren't in a cell, 1 means we are in a cell,
48 * an greater numbers mean we are in a cell nested within a cell. */
49 UINT nNestingLevel;
50 CHARFORMAT2W cur_fmt; /* current character format */
51 } ME_OutStream;
53 static BOOL
54 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
57 static ME_OutStream*
58 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
60 ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream);
61 pStream->stream = stream;
62 pStream->stream->dwError = 0;
63 pStream->pos = 0;
64 pStream->written = 0;
65 pStream->nFontTblLen = 0;
66 pStream->nColorTblLen = 1;
67 pStream->nNestingLevel = 0;
68 memset(&pStream->cur_fmt, 0, sizeof(pStream->cur_fmt));
69 pStream->cur_fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
70 pStream->cur_fmt.bUnderlineType = CFU_UNDERLINE;
71 return pStream;
75 static BOOL
76 ME_StreamOutFlush(ME_OutStream *pStream)
78 LONG nWritten = 0;
79 EDITSTREAM *stream = pStream->stream;
81 if (pStream->pos) {
82 TRACE("sending %u bytes\n", pStream->pos);
83 nWritten = pStream->pos;
84 stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer,
85 pStream->pos, &nWritten);
86 TRACE("error=%u written=%u\n", stream->dwError, nWritten);
87 if (nWritten == 0 || stream->dwError)
88 return FALSE;
89 /* Don't resend partial chunks if nWritten < pStream->pos */
91 if (nWritten == pStream->pos)
92 pStream->written += nWritten;
93 pStream->pos = 0;
94 return TRUE;
98 static LONG
99 ME_StreamOutFree(ME_OutStream *pStream)
101 LONG written = pStream->written;
102 TRACE("total length = %u\n", written);
104 FREE_OBJ(pStream);
105 return written;
109 static BOOL
110 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
112 while (len) {
113 int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
114 int fit = min(space, len);
116 TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
117 memmove(pStream->buffer + pStream->pos, buffer, fit);
118 len -= fit;
119 buffer += fit;
120 pStream->pos += fit;
121 if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
122 if (!ME_StreamOutFlush(pStream))
123 return FALSE;
126 return TRUE;
130 static BOOL
131 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
133 char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
134 int len;
135 va_list valist;
137 va_start(valist, format);
138 len = vsnprintf(string, sizeof(string), format, valist);
139 va_end(valist);
141 return ME_StreamOutMove(pStream, string, len);
144 #define HEX_BYTES_PER_LINE 40
146 static BOOL
147 ME_StreamOutHexData(ME_OutStream *stream, const BYTE *data, UINT len)
150 char line[HEX_BYTES_PER_LINE * 2 + 1];
151 UINT size, i;
152 static const char hex[] = "0123456789abcdef";
154 while (len)
156 size = min( len, HEX_BYTES_PER_LINE );
157 for (i = 0; i < size; i++)
159 line[i * 2] = hex[(*data >> 4) & 0xf];
160 line[i * 2 + 1] = hex[*data & 0xf];
161 data++;
163 line[size * 2] = '\n';
164 if (!ME_StreamOutMove( stream, line, size * 2 + 1 ))
165 return FALSE;
166 len -= size;
168 return TRUE;
171 static BOOL
172 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
174 const char *cCharSet = NULL;
175 UINT nCodePage;
176 LANGID language;
177 BOOL success;
179 if (dwFormat & SF_USECODEPAGE) {
180 CPINFOEXW info;
182 switch (HIWORD(dwFormat)) {
183 case CP_ACP:
184 cCharSet = "ansi";
185 nCodePage = GetACP();
186 break;
187 case CP_OEMCP:
188 nCodePage = GetOEMCP();
189 if (nCodePage == 437)
190 cCharSet = "pc";
191 else if (nCodePage == 850)
192 cCharSet = "pca";
193 else
194 cCharSet = "ansi";
195 break;
196 case CP_UTF8:
197 nCodePage = CP_UTF8;
198 break;
199 default:
200 if (HIWORD(dwFormat) == CP_MACCP) {
201 cCharSet = "mac";
202 nCodePage = 10000; /* MacRoman */
203 } else {
204 cCharSet = "ansi";
205 nCodePage = 1252; /* Latin-1 */
207 if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
208 nCodePage = info.CodePage;
210 } else {
211 cCharSet = "ansi";
212 /* TODO: If the original document contained an \ansicpg value, retain it.
213 * Otherwise, M$ richedit emits a codepage number determined from the
214 * charset of the default font here. Anyway, this value is not used by
215 * the reader... */
216 nCodePage = GetACP();
218 if (nCodePage == CP_UTF8)
219 success = ME_StreamOutPrint(pStream, "{\\urtf");
220 else
221 success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
223 if (!success)
224 return FALSE;
226 pStream->nDefaultCodePage = nCodePage;
228 /* FIXME: This should be a document property */
229 /* TODO: handle SFF_PLAINRTF */
230 language = GetUserDefaultLangID();
231 if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
232 return FALSE;
234 /* FIXME: This should be a document property */
235 pStream->nDefaultFont = 0;
237 return TRUE;
240 static void add_font_to_fonttbl( ME_OutStream *stream, ME_Style *style )
242 ME_FontTableItem *table = stream->fonttbl;
243 CHARFORMAT2W *fmt = &style->fmt;
244 WCHAR *face = fmt->szFaceName;
245 BYTE charset = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
246 int i;
248 if (fmt->dwMask & CFM_FACE)
250 for (i = 0; i < stream->nFontTblLen; i++)
251 if (table[i].bCharSet == charset
252 && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face)))
253 break;
255 if (i == stream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE)
257 table[i].bCharSet = charset;
258 table[i].szFaceName = face;
259 stream->nFontTblLen++;
264 static BOOL find_font_in_fonttbl( ME_OutStream *stream, CHARFORMAT2W *fmt, unsigned int *idx )
266 WCHAR *facename;
267 int i;
269 *idx = 0;
270 if (fmt->dwMask & CFM_FACE)
271 facename = fmt->szFaceName;
272 else
273 facename = stream->fonttbl[0].szFaceName;
274 for (i = 0; i < stream->nFontTblLen; i++)
276 if (facename == stream->fonttbl[i].szFaceName
277 || !lstrcmpW(facename, stream->fonttbl[i].szFaceName))
278 if (!(fmt->dwMask & CFM_CHARSET)
279 || fmt->bCharSet == stream->fonttbl[i].bCharSet)
281 *idx = i;
282 break;
286 return i < stream->nFontTblLen;
289 static void add_color_to_colortbl( ME_OutStream *stream, COLORREF color )
291 int i;
293 for (i = 1; i < stream->nColorTblLen; i++)
294 if (stream->colortbl[i] == color)
295 break;
297 if (i == stream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE)
299 stream->colortbl[i] = color;
300 stream->nColorTblLen++;
304 static BOOL find_color_in_colortbl( ME_OutStream *stream, COLORREF color, unsigned int *idx )
306 int i;
308 *idx = 0;
309 for (i = 1; i < stream->nColorTblLen; i++)
311 if (stream->colortbl[i] == color)
313 *idx = i;
314 break;
318 return i < stream->nFontTblLen;
321 static BOOL
322 ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun,
323 ME_DisplayItem *pLastRun)
325 ME_DisplayItem *item = pFirstRun;
326 ME_FontTableItem *table = pStream->fonttbl;
327 unsigned int i;
328 ME_DisplayItem *pCell = NULL;
329 ME_Paragraph *prev_para = NULL;
331 do {
332 CHARFORMAT2W *fmt = &item->member.run.style->fmt;
334 add_font_to_fonttbl( pStream, item->member.run.style );
336 if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR))
337 add_color_to_colortbl( pStream, fmt->crTextColor );
338 if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR))
339 add_color_to_colortbl( pStream, fmt->crBackColor );
341 if (item->member.run.para != prev_para)
343 /* check for any para numbering text */
344 if (item->member.run.para->fmt.wNumbering)
345 add_font_to_fonttbl( pStream, item->member.run.para->para_num.style );
347 if ((pCell = item->member.para.pCell))
349 ME_Border* borders[4] = { &pCell->member.cell.border.top,
350 &pCell->member.cell.border.left,
351 &pCell->member.cell.border.bottom,
352 &pCell->member.cell.border.right };
353 for (i = 0; i < 4; i++)
354 if (borders[i]->width > 0)
355 add_color_to_colortbl( pStream, borders[i]->colorRef );
358 prev_para = item->member.run.para;
361 if (item == pLastRun)
362 break;
363 item = ME_FindItemFwd(item, diRun);
364 } while (item);
366 if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
367 return FALSE;
369 for (i = 0; i < pStream->nFontTblLen; i++) {
370 if (table[i].bCharSet != DEFAULT_CHARSET) {
371 if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
372 return FALSE;
373 } else {
374 if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
375 return FALSE;
377 if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
378 return FALSE;
379 if (!ME_StreamOutPrint(pStream, ";}"))
380 return FALSE;
382 if (!ME_StreamOutPrint(pStream, "}\r\n"))
383 return FALSE;
385 /* It seems like Open Office ignores \deff0 tag at RTF-header.
386 As result it can't correctly parse text before first \fN tag,
387 so we can put \f0 immediately after font table. This forces
388 parser to use the same font, that \deff0 specifies.
389 It makes OOffice happy */
390 if (!ME_StreamOutPrint(pStream, "\\f0"))
391 return FALSE;
393 /* Output the color table */
394 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
395 for (i = 1; i < pStream->nColorTblLen; i++)
397 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
398 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
399 return FALSE;
401 if (!ME_StreamOutPrint(pStream, "}")) return FALSE;
403 return TRUE;
406 static BOOL
407 ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream,
408 ME_DisplayItem *para)
410 ME_DisplayItem *cell;
411 char props[STREAMOUT_BUFFER_SIZE] = "";
412 int i;
413 const char sideChar[4] = {'t','l','b','r'};
415 if (!ME_StreamOutPrint(pStream, "\\trowd"))
416 return FALSE;
417 if (!editor->bEmulateVersion10) { /* v4.1 */
418 PARAFORMAT2 *pFmt = &ME_GetTableRowEnd(para)->member.para.fmt;
419 para = ME_GetTableRowStart(para);
420 cell = para->member.para.next_para->member.para.pCell;
421 assert(cell);
422 if (pFmt->dxOffset)
423 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
424 if (pFmt->dxStartIndent)
425 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
426 do {
427 ME_Border* borders[4] = { &cell->member.cell.border.top,
428 &cell->member.cell.border.left,
429 &cell->member.cell.border.bottom,
430 &cell->member.cell.border.right };
431 for (i = 0; i < 4; i++)
433 if (borders[i]->width)
435 unsigned int idx;
436 COLORREF crColor = borders[i]->colorRef;
437 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
438 sprintf(props + strlen(props), "\\brdrs");
439 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
440 if (find_color_in_colortbl( pStream, crColor, &idx ))
441 sprintf(props + strlen(props), "\\brdrcf%u", idx);
444 sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary);
445 cell = cell->member.cell.next_cell;
446 } while (cell->member.cell.next_cell);
447 } else { /* v1.0 - 3.0 */
448 const ME_Border* borders[4] = { &para->member.para.border.top,
449 &para->member.para.border.left,
450 &para->member.para.border.bottom,
451 &para->member.para.border.right };
452 PARAFORMAT2 *pFmt = &para->member.para.fmt;
454 assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)));
455 if (pFmt->dxOffset)
456 sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset);
457 if (pFmt->dxStartIndent)
458 sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent);
459 for (i = 0; i < 4; i++)
461 if (borders[i]->width)
463 unsigned int idx;
464 COLORREF crColor = borders[i]->colorRef;
465 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
466 sprintf(props + strlen(props), "\\brdrs");
467 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
468 if (find_color_in_colortbl( pStream, crColor, &idx ))
469 sprintf(props + strlen(props), "\\brdrcf%u", idx);
472 for (i = 0; i < pFmt->cTabCount; i++)
474 sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF);
477 if (!ME_StreamOutPrint(pStream, props))
478 return FALSE;
479 props[0] = '\0';
480 return TRUE;
483 static BOOL stream_out_para_num( ME_OutStream *stream, ME_Paragraph *para, BOOL pn_dest )
485 static const char fmt_label[] = "{\\*\\pn\\pnlvlbody\\pnf%u\\pnindent%d\\pnstart%d%s%s}";
486 static const char fmt_bullet[] = "{\\*\\pn\\pnlvlblt\\pnf%u\\pnindent%d{\\pntxtb\\'b7}}";
487 static const char dec[] = "\\pndec";
488 static const char lcltr[] = "\\pnlcltr";
489 static const char ucltr[] = "\\pnucltr";
490 static const char lcrm[] = "\\pnlcrm";
491 static const char ucrm[] = "\\pnucrm";
492 static const char period[] = "{\\pntxta.}";
493 static const char paren[] = "{\\pntxta)}";
494 static const char parens[] = "{\\pntxtb(}{\\pntxta)}";
495 const char *type, *style = "";
496 unsigned int idx;
498 find_font_in_fonttbl( stream, &para->para_num.style->fmt, &idx );
500 if (!ME_StreamOutPrint( stream, "{\\pntext\\f%u ", idx )) return FALSE;
501 if (!ME_StreamOutRTFText( stream, para->para_num.text->szData, para->para_num.text->nLen ))
502 return FALSE;
503 if (!ME_StreamOutPrint( stream, "\\tab}" )) return FALSE;
505 if (!pn_dest) return TRUE;
507 if (para->fmt.wNumbering == PFN_BULLET)
509 if (!ME_StreamOutPrint( stream, fmt_bullet, idx, para->fmt.wNumberingTab ))
510 return FALSE;
512 else
514 switch (para->fmt.wNumbering)
516 case PFN_ARABIC:
517 default:
518 type = dec;
519 break;
520 case PFN_LCLETTER:
521 type = lcltr;
522 break;
523 case PFN_UCLETTER:
524 type = ucltr;
525 break;
526 case PFN_LCROMAN:
527 type = lcrm;
528 break;
529 case PFN_UCROMAN:
530 type = ucrm;
531 break;
533 switch (para->fmt.wNumberingStyle & 0xf00)
535 case PFNS_PERIOD:
536 style = period;
537 break;
538 case PFNS_PAREN:
539 style = paren;
540 break;
541 case PFNS_PARENS:
542 style = parens;
543 break;
546 if (!ME_StreamOutPrint( stream, fmt_label, idx, para->fmt.wNumberingTab,
547 para->fmt.wNumberingStart, type, style ))
548 return FALSE;
550 return TRUE;
553 static BOOL
554 ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream,
555 ME_DisplayItem *para)
557 PARAFORMAT2 *fmt = &para->member.para.fmt;
558 char props[STREAMOUT_BUFFER_SIZE] = "";
559 int i;
560 ME_Paragraph *prev_para = NULL;
562 if (para->member.para.prev_para->type == diParagraph)
563 prev_para = &para->member.para.prev_para->member.para;
565 if (!editor->bEmulateVersion10) { /* v4.1 */
566 if (para->member.para.nFlags & MEPF_ROWSTART) {
567 pStream->nNestingLevel++;
568 if (pStream->nNestingLevel == 1) {
569 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
570 return FALSE;
572 return TRUE;
573 } else if (para->member.para.nFlags & MEPF_ROWEND) {
574 pStream->nNestingLevel--;
575 if (pStream->nNestingLevel >= 1) {
576 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops"))
577 return FALSE;
578 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
579 return FALSE;
580 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n"))
581 return FALSE;
582 } else {
583 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
584 return FALSE;
586 return TRUE;
588 } else { /* v1.0 - 3.0 */
589 if (para->member.para.fmt.dwMask & PFM_TABLE &&
590 para->member.para.fmt.wEffects & PFE_TABLE)
592 if (!ME_StreamOutRTFTableProps(editor, pStream, para))
593 return FALSE;
597 if (prev_para && !memcmp( fmt, &prev_para->fmt, sizeof(*fmt) ))
599 if (fmt->wNumbering)
600 return stream_out_para_num( pStream, &para->member.para, FALSE );
601 return TRUE;
604 if (!ME_StreamOutPrint(pStream, "\\pard"))
605 return FALSE;
607 if (fmt->wNumbering)
608 if (!stream_out_para_num( pStream, &para->member.para, TRUE )) return FALSE;
610 if (!editor->bEmulateVersion10) { /* v4.1 */
611 if (pStream->nNestingLevel > 0)
612 strcat(props, "\\intbl");
613 if (pStream->nNestingLevel > 1)
614 sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
615 } else { /* v1.0 - 3.0 */
616 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
617 strcat(props, "\\intbl");
620 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
621 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
622 * set very different from the documentation.
623 * (Tested with RichEdit 5.50.25.0601) */
625 if (fmt->dwMask & PFM_ALIGNMENT) {
626 switch (fmt->wAlignment) {
627 case PFA_LEFT:
628 /* Default alignment: not emitted */
629 break;
630 case PFA_RIGHT:
631 strcat(props, "\\qr");
632 break;
633 case PFA_CENTER:
634 strcat(props, "\\qc");
635 break;
636 case PFA_JUSTIFY:
637 strcat(props, "\\qj");
638 break;
642 if (fmt->dwMask & PFM_LINESPACING) {
643 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
644 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
645 switch (fmt->bLineSpacingRule) {
646 case 0: /* Single spacing */
647 strcat(props, "\\sl-240\\slmult1");
648 break;
649 case 1: /* 1.5 spacing */
650 strcat(props, "\\sl-360\\slmult1");
651 break;
652 case 2: /* Double spacing */
653 strcat(props, "\\sl-480\\slmult1");
654 break;
655 case 3:
656 sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing);
657 break;
658 case 4:
659 sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing);
660 break;
661 case 5:
662 sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20);
663 break;
667 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
668 strcat(props, "\\hyph0");
669 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
670 strcat(props, "\\keep");
671 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
672 strcat(props, "\\keepn");
673 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
674 strcat(props, "\\noline");
675 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
676 strcat(props, "\\nowidctlpar");
677 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
678 strcat(props, "\\pagebb");
679 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
680 strcat(props, "\\rtlpar");
681 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
682 strcat(props, "\\sbys");
684 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
685 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
687 if (fmt->dxOffset)
688 sprintf(props + strlen(props), "\\li%d", fmt->dxOffset);
689 if (fmt->dxStartIndent)
690 sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent);
691 if (fmt->dxRightIndent)
692 sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent);
693 if (fmt->dwMask & PFM_TABSTOPS) {
694 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
696 for (i = 0; i < fmt->cTabCount; i++) {
697 switch ((fmt->rgxTabs[i] >> 24) & 0xF) {
698 case 1:
699 strcat(props, "\\tqc");
700 break;
701 case 2:
702 strcat(props, "\\tqr");
703 break;
704 case 3:
705 strcat(props, "\\tqdec");
706 break;
707 case 4:
708 /* Word bar tab (vertical bar). Handled below */
709 break;
711 if (fmt->rgxTabs[i] >> 28 <= 5)
712 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
713 sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF);
717 if (fmt->dySpaceAfter)
718 sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter);
719 if (fmt->dySpaceBefore)
720 sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore);
721 if (fmt->sStyle != -1)
722 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
724 if (fmt->dwMask & PFM_SHADING) {
725 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
726 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
727 "\\bghoriz", "\\bgvert", "\\bgfdiag",
728 "\\bgbdiag", "\\bgcross", "\\bgdcross",
729 "", "", "" };
730 if (fmt->wShadingWeight)
731 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
732 if (fmt->wShadingStyle & 0xF)
733 strcat(props, style[fmt->wShadingStyle & 0xF]);
734 sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d",
735 (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF);
737 if (*props)
738 strcat(props, " ");
740 if (*props && !ME_StreamOutPrint(pStream, props))
741 return FALSE;
743 return TRUE;
747 static BOOL
748 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
750 char props[STREAMOUT_BUFFER_SIZE] = "";
751 unsigned int i;
752 CHARFORMAT2W *old_fmt = &pStream->cur_fmt;
753 static const struct
755 DWORD effect;
756 const char *on, *off;
757 } effects[] =
759 { CFE_ALLCAPS, "\\caps", "\\caps0" },
760 { CFE_BOLD, "\\b", "\\b0" },
761 { CFE_DISABLED, "\\disabled", "\\disabled0" },
762 { CFE_EMBOSS, "\\embo", "\\embo0" },
763 { CFE_HIDDEN, "\\v", "\\v0" },
764 { CFE_IMPRINT, "\\impr", "\\impr0" },
765 { CFE_ITALIC, "\\i", "\\i0" },
766 { CFE_OUTLINE, "\\outl", "\\outl0" },
767 { CFE_PROTECTED, "\\protect", "\\protect0" },
768 { CFE_SHADOW, "\\shad", "\\shad0" },
769 { CFE_SMALLCAPS, "\\scaps", "\\scaps0" },
770 { CFE_STRIKEOUT, "\\strike", "\\strike0" },
773 for (i = 0; i < sizeof(effects) / sizeof(effects[0]); i++)
775 if ((old_fmt->dwEffects ^ fmt->dwEffects) & effects[i].effect)
776 strcat( props, fmt->dwEffects & effects[i].effect ? effects[i].on : effects[i].off );
779 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOBACKCOLOR ||
780 old_fmt->crBackColor != fmt->crBackColor)
782 if (fmt->dwEffects & CFE_AUTOBACKCOLOR) i = 0;
783 else find_color_in_colortbl( pStream, fmt->crBackColor, &i );
784 sprintf(props + strlen(props), "\\cb%u", i);
786 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOCOLOR ||
787 old_fmt->crTextColor != fmt->crTextColor)
789 if (fmt->dwEffects & CFE_AUTOCOLOR) i = 0;
790 else find_color_in_colortbl( pStream, fmt->crTextColor, &i );
791 sprintf(props + strlen(props), "\\cf%u", i);
794 if (old_fmt->bAnimation != fmt->bAnimation)
795 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
796 if (old_fmt->wKerning != fmt->wKerning)
797 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
799 if (old_fmt->lcid != fmt->lcid)
801 /* TODO: handle SFF_PLAINRTF */
802 if (LOWORD(fmt->lcid) == 1024)
803 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
804 else
805 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
808 if (old_fmt->yOffset != fmt->yOffset)
810 if (fmt->yOffset >= 0)
811 sprintf(props + strlen(props), "\\up%d", fmt->yOffset);
812 else
813 sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset);
815 if (old_fmt->yHeight != fmt->yHeight)
816 sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10);
817 if (old_fmt->sSpacing != fmt->sSpacing)
818 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
819 if ((old_fmt->dwEffects ^ fmt->dwEffects) & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT))
821 if (fmt->dwEffects & CFE_SUBSCRIPT)
822 strcat(props, "\\sub");
823 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
824 strcat(props, "\\super");
825 else
826 strcat(props, "\\nosupersub");
828 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_UNDERLINE ||
829 old_fmt->bUnderlineType != fmt->bUnderlineType)
831 BYTE type = (fmt->dwEffects & CFE_UNDERLINE) ? fmt->bUnderlineType : CFU_UNDERLINENONE;
832 switch (type)
834 case CFU_UNDERLINE:
835 strcat(props, "\\ul");
836 break;
837 case CFU_UNDERLINEDOTTED:
838 strcat(props, "\\uld");
839 break;
840 case CFU_UNDERLINEDOUBLE:
841 strcat(props, "\\uldb");
842 break;
843 case CFU_UNDERLINEWORD:
844 strcat(props, "\\ulw");
845 break;
846 case CFU_CF1UNDERLINE:
847 case CFU_UNDERLINENONE:
848 default:
849 strcat(props, "\\ulnone");
850 break;
854 if (strcmpW(old_fmt->szFaceName, fmt->szFaceName) ||
855 old_fmt->bCharSet != fmt->bCharSet)
857 if (find_font_in_fonttbl( pStream, fmt, &i ))
859 sprintf(props + strlen(props), "\\f%u", i);
861 /* In UTF-8 mode, charsets/codepages are not used */
862 if (pStream->nDefaultCodePage != CP_UTF8)
864 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
865 pStream->nCodePage = pStream->nDefaultCodePage;
866 else
867 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
871 if (*props)
872 strcat(props, " ");
873 if (!ME_StreamOutPrint(pStream, props))
874 return FALSE;
875 *old_fmt = *fmt;
876 return TRUE;
880 static BOOL
881 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
883 char buffer[STREAMOUT_BUFFER_SIZE];
884 int pos = 0;
885 int fit, nBytes, i;
887 if (nChars == -1)
888 nChars = lstrlenW(text);
890 while (nChars) {
891 /* In UTF-8 mode, font charsets are not used. */
892 if (pStream->nDefaultCodePage == CP_UTF8) {
893 /* 6 is the maximum character length in UTF-8 */
894 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
895 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
896 STREAMOUT_BUFFER_SIZE, NULL, NULL);
897 nChars -= fit;
898 text += fit;
899 for (i = 0; i < nBytes; i++)
900 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
901 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
902 return FALSE;
903 pos = i;
905 if (pos < nBytes)
906 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
907 return FALSE;
908 pos = 0;
909 } else if (*text < 128) {
910 if (*text == '{' || *text == '}' || *text == '\\')
911 buffer[pos++] = '\\';
912 buffer[pos++] = (char)(*text++);
913 nChars--;
914 } else {
915 BOOL unknown = FALSE;
916 char letter[3];
918 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
919 * codepages including CP_SYMBOL for which the last parameter must be set
920 * to NULL for the function to succeed. But in Wine we need to care only
921 * about CP_SYMBOL */
922 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
923 letter, 3, NULL,
924 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
925 if (unknown)
926 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
927 else if ((BYTE)*letter < 128) {
928 if (*letter == '{' || *letter == '}' || *letter == '\\')
929 buffer[pos++] = '\\';
930 buffer[pos++] = *letter;
931 } else {
932 for (i = 0; i < nBytes; i++)
933 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
935 text++;
936 nChars--;
938 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
939 if (!ME_StreamOutMove(pStream, buffer, pos))
940 return FALSE;
941 pos = 0;
944 return ME_StreamOutMove(pStream, buffer, pos);
947 static BOOL stream_out_graphics( ME_TextEditor *editor, ME_OutStream *stream,
948 ME_Run *run )
950 IDataObject *data;
951 HRESULT hr;
952 FORMATETC fmt = { CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF };
953 STGMEDIUM med = { TYMED_NULL };
954 BOOL ret = FALSE;
955 ENHMETAHEADER *emf_bits = NULL;
956 UINT size;
957 SIZE goal, pic;
958 ME_Context c;
960 hr = IOleObject_QueryInterface( run->ole_obj->poleobj, &IID_IDataObject, (void **)&data );
961 if (FAILED(hr)) return FALSE;
963 ME_InitContext( &c, editor, ITextHost_TxGetDC( editor->texthost ) );
964 hr = IDataObject_QueryGetData( data, &fmt );
965 if (hr != S_OK) goto done;
967 hr = IDataObject_GetData( data, &fmt, &med );
968 if (FAILED(hr)) goto done;
969 if (med.tymed != TYMED_ENHMF) goto done;
971 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, 0, NULL );
972 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
974 emf_bits = HeapAlloc( GetProcessHeap(), 0, size );
975 if (!emf_bits) goto done;
977 size = GetEnhMetaFileBits( med.u.hEnhMetaFile, size, (BYTE *)emf_bits );
978 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
980 /* size_in_pixels = (frame_size / 100) * szlDevice / szlMillimeters
981 pic = size_in_pixels * 2540 / dpi */
982 pic.cx = MulDiv( emf_bits->rclFrame.right - emf_bits->rclFrame.left, emf_bits->szlDevice.cx * 254,
983 emf_bits->szlMillimeters.cx * c.dpi.cx * 10 );
984 pic.cy = MulDiv( emf_bits->rclFrame.bottom - emf_bits->rclFrame.top, emf_bits->szlDevice.cy * 254,
985 emf_bits->szlMillimeters.cy * c.dpi.cy * 10 );
987 /* convert goal size to twips */
988 goal.cx = MulDiv( run->ole_obj->sizel.cx, 144, 254 );
989 goal.cy = MulDiv( run->ole_obj->sizel.cy, 144, 254 );
991 if (!ME_StreamOutPrint( stream, "{\\*\\shppict{\\pict\\emfblip\\picw%d\\pich%d\\picwgoal%d\\pichgoal%d\n",
992 pic.cx, pic.cy, goal.cx, goal.cy ))
993 goto done;
995 if (!ME_StreamOutHexData( stream, (BYTE *)emf_bits, size ))
996 goto done;
998 if (!ME_StreamOutPrint( stream, "}}\n" ))
999 goto done;
1001 ret = TRUE;
1003 done:
1004 ME_DestroyContext( &c );
1005 HeapFree( GetProcessHeap(), 0, emf_bits );
1006 ReleaseStgMedium( &med );
1007 IDataObject_Release( data );
1008 return ret;
1011 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
1012 const ME_Cursor *start, int nChars, int dwFormat)
1014 ME_Cursor cursor = *start;
1015 ME_DisplayItem *prev_para = NULL;
1016 ME_Cursor endCur = cursor;
1018 ME_MoveCursorChars(editor, &endCur, nChars, TRUE);
1020 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
1021 return FALSE;
1023 if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun))
1024 return FALSE;
1026 /* TODO: stylesheet table */
1028 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0;}"))
1029 return FALSE;
1031 /* TODO: information group */
1033 /* TODO: document formatting properties */
1035 /* FIXME: We have only one document section */
1037 /* TODO: section formatting properties */
1039 do {
1040 if (cursor.pPara != prev_para)
1042 prev_para = cursor.pPara;
1043 if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara))
1044 return FALSE;
1047 if (cursor.pRun == endCur.pRun && !endCur.nOffset)
1048 break;
1049 TRACE("flags %xh\n", cursor.pRun->member.run.nFlags);
1050 /* TODO: emit embedded objects */
1051 if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND))
1052 continue;
1053 if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) {
1054 if (!stream_out_graphics(editor, pStream, &cursor.pRun->member.run))
1055 return FALSE;
1056 } else if (cursor.pRun->member.run.nFlags & MERF_TAB) {
1057 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
1058 cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1059 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE)
1061 if (!ME_StreamOutPrint(pStream, "\\cell "))
1062 return FALSE;
1063 } else {
1064 if (!ME_StreamOutPrint(pStream, "\\tab "))
1065 return FALSE;
1067 } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) {
1068 if (pStream->nNestingLevel > 1) {
1069 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
1070 return FALSE;
1071 } else {
1072 if (!ME_StreamOutPrint(pStream, "\\cell "))
1073 return FALSE;
1075 nChars--;
1076 } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) {
1077 if (cursor.pPara->member.para.fmt.dwMask & PFM_TABLE &&
1078 cursor.pPara->member.para.fmt.wEffects & PFE_TABLE &&
1079 !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL)))
1081 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
1082 return FALSE;
1083 } else {
1084 if (!ME_StreamOutPrint(pStream, "\\par\r\n"))
1085 return FALSE;
1087 /* Skip as many characters as required by current line break */
1088 nChars = max(0, nChars - cursor.pRun->member.run.len);
1089 } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) {
1090 if (!ME_StreamOutPrint(pStream, "\\line\r\n"))
1091 return FALSE;
1092 nChars--;
1093 } else {
1094 int nEnd;
1096 TRACE("style %p\n", cursor.pRun->member.run.style);
1097 if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt))
1098 return FALSE;
1100 nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len;
1101 if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1102 nEnd - cursor.nOffset))
1103 return FALSE;
1104 cursor.nOffset = 0;
1106 } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE));
1108 if (!ME_StreamOutMove(pStream, "}\0", 2))
1109 return FALSE;
1110 return TRUE;
1114 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
1115 const ME_Cursor *start, int nChars, DWORD dwFormat)
1117 ME_Cursor cursor = *start;
1118 int nLen;
1119 UINT nCodePage = CP_ACP;
1120 char *buffer = NULL;
1121 int nBufLen = 0;
1122 BOOL success = TRUE;
1124 if (!cursor.pRun)
1125 return FALSE;
1127 if (dwFormat & SF_USECODEPAGE)
1128 nCodePage = HIWORD(dwFormat);
1130 /* TODO: Handle SF_TEXTIZED */
1132 while (success && nChars && cursor.pRun) {
1133 nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset);
1135 if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA)
1137 static const WCHAR szEOL[] = { '\r', '\n' };
1139 /* richedit 2.0 - all line breaks are \r\n */
1140 if (dwFormat & SF_UNICODE)
1141 success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL));
1142 else
1143 success = ME_StreamOutMove(pStream, "\r\n", 2);
1144 } else {
1145 if (dwFormat & SF_UNICODE)
1146 success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )),
1147 sizeof(WCHAR) * nLen);
1148 else {
1149 int nSize;
1151 nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1152 nLen, NULL, 0, NULL, NULL);
1153 if (nSize > nBufLen) {
1154 FREE_OBJ(buffer);
1155 buffer = ALLOC_N_OBJ(char, nSize);
1156 nBufLen = nSize;
1158 WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ),
1159 nLen, buffer, nSize, NULL, NULL);
1160 success = ME_StreamOutMove(pStream, buffer, nSize);
1164 nChars -= nLen;
1165 cursor.nOffset = 0;
1166 cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun);
1169 FREE_OBJ(buffer);
1170 return success;
1174 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
1175 const ME_Cursor *start,
1176 int nChars, EDITSTREAM *stream)
1178 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
1180 if (dwFormat & SF_RTF)
1181 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
1182 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
1183 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
1184 if (!pStream->stream->dwError)
1185 ME_StreamOutFlush(pStream);
1186 return ME_StreamOutFree(pStream);
1189 LRESULT
1190 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
1192 ME_Cursor start;
1193 int nChars;
1195 if (dwFormat & SFF_SELECTION) {
1196 int nStart, nTo;
1197 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
1198 nChars = nTo - nStart;
1199 } else {
1200 ME_SetCursorToStart(editor, &start);
1201 nChars = ME_GetTextLength(editor);
1202 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
1203 if (dwFormat & SF_RTF)
1204 nChars++;
1206 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);