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
22 #include "wine/port.h"
24 #define NONAMELESSUNION
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
38 char buffer
[STREAMOUT_BUFFER_SIZE
];
42 ME_FontTableItem fonttbl
[STREAMOUT_FONTTBL_SIZE
];
44 COLORREF colortbl
[STREAMOUT_COLORTBL_SIZE
];
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. */
50 CHARFORMAT2W cur_fmt
; /* current character format */
54 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
);
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;
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
;
76 ME_StreamOutFlush(ME_OutStream
*pStream
)
79 EDITSTREAM
*stream
= pStream
->stream
;
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
)
89 /* Don't resend partial chunks if nWritten < pStream->pos */
91 if (nWritten
== pStream
->pos
)
92 pStream
->written
+= nWritten
;
99 ME_StreamOutFree(ME_OutStream
*pStream
)
101 LONG written
= pStream
->written
;
102 TRACE("total length = %u\n", written
);
110 ME_StreamOutMove(ME_OutStream
*pStream
, const char *buffer
, int 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
);
121 if (pStream
->pos
== STREAMOUT_BUFFER_SIZE
) {
122 if (!ME_StreamOutFlush(pStream
))
131 ME_StreamOutPrint(ME_OutStream
*pStream
, const char *format
, ...)
133 char string
[STREAMOUT_BUFFER_SIZE
]; /* This is going to be enough */
137 va_start(valist
, format
);
138 len
= vsnprintf(string
, sizeof(string
), format
, valist
);
141 return ME_StreamOutMove(pStream
, string
, len
);
144 #define HEX_BYTES_PER_LINE 40
147 ME_StreamOutHexData(ME_OutStream
*stream
, const BYTE
*data
, UINT len
)
150 char line
[HEX_BYTES_PER_LINE
* 2 + 1];
152 static const char hex
[] = "0123456789abcdef";
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];
163 line
[size
* 2] = '\n';
164 if (!ME_StreamOutMove( stream
, line
, size
* 2 + 1 ))
172 ME_StreamOutRTFHeader(ME_OutStream
*pStream
, int dwFormat
)
174 const char *cCharSet
= NULL
;
179 if (dwFormat
& SF_USECODEPAGE
) {
182 switch (HIWORD(dwFormat
)) {
185 nCodePage
= GetACP();
188 nCodePage
= GetOEMCP();
189 if (nCodePage
== 437)
191 else if (nCodePage
== 850)
200 if (HIWORD(dwFormat
) == CP_MACCP
) {
202 nCodePage
= 10000; /* MacRoman */
205 nCodePage
= 1252; /* Latin-1 */
207 if (GetCPInfoExW(HIWORD(dwFormat
), 0, &info
))
208 nCodePage
= info
.CodePage
;
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
216 nCodePage
= GetACP();
218 if (nCodePage
== CP_UTF8
)
219 success
= ME_StreamOutPrint(pStream
, "{\\urtf");
221 success
= ME_StreamOutPrint(pStream
, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet
, nCodePage
);
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
))
234 /* FIXME: This should be a document property */
235 pStream
->nDefaultFont
= 0;
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
;
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
)))
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
)
270 if (fmt
->dwMask
& CFM_FACE
)
271 facename
= fmt
->szFaceName
;
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
)
286 return i
< stream
->nFontTblLen
;
289 static void add_color_to_colortbl( ME_OutStream
*stream
, COLORREF color
)
293 for (i
= 1; i
< stream
->nColorTblLen
; i
++)
294 if (stream
->colortbl
[i
] == color
)
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
)
309 for (i
= 1; i
< stream
->nColorTblLen
; i
++)
311 if (stream
->colortbl
[i
] == color
)
318 return i
< stream
->nFontTblLen
;
322 ME_StreamOutRTFFontAndColorTbl(ME_OutStream
*pStream
, ME_DisplayItem
*pFirstRun
,
323 ME_DisplayItem
*pLastRun
)
325 ME_DisplayItem
*item
= pFirstRun
;
326 ME_FontTableItem
*table
= pStream
->fonttbl
;
328 ME_DisplayItem
*pCell
= NULL
;
329 ME_Paragraph
*prev_para
= NULL
;
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
)
363 item
= ME_FindItemFwd(item
, diRun
);
366 if (!ME_StreamOutPrint(pStream
, "{\\fonttbl"))
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
))
374 if (!ME_StreamOutPrint(pStream
, "{\\f%u ", i
))
377 if (!ME_StreamOutRTFText(pStream
, table
[i
].szFaceName
, -1))
379 if (!ME_StreamOutPrint(pStream
, ";}"))
382 if (!ME_StreamOutPrint(pStream
, "}\r\n"))
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"))
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))
401 if (!ME_StreamOutPrint(pStream
, "}")) return FALSE
;
407 ME_StreamOutRTFTableProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
408 ME_DisplayItem
*para
)
410 ME_DisplayItem
*cell
;
411 char props
[STREAMOUT_BUFFER_SIZE
] = "";
413 const char sideChar
[4] = {'t','l','b','r'};
415 if (!ME_StreamOutPrint(pStream
, "\\trowd"))
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
;
423 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
424 if (pFmt
->dxStartIndent
)
425 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
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
)
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] = { ¶
->member
.para
.border
.top
,
449 ¶
->member
.para
.border
.left
,
450 ¶
->member
.para
.border
.bottom
,
451 ¶
->member
.para
.border
.right
};
452 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
454 assert(!(para
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)));
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
)
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
))
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
= "";
498 find_font_in_fonttbl( stream
, ¶
->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
))
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
))
514 switch (para
->fmt
.wNumbering
)
533 switch (para
->fmt
.wNumberingStyle
& 0xf00)
546 if (!ME_StreamOutPrint( stream
, fmt_label
, idx
, para
->fmt
.wNumberingTab
,
547 para
->fmt
.wNumberingStart
, type
, style
))
554 ME_StreamOutRTFParaProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
555 ME_DisplayItem
*para
)
557 PARAFORMAT2
*fmt
= ¶
->member
.para
.fmt
;
558 char props
[STREAMOUT_BUFFER_SIZE
] = "";
560 ME_Paragraph
*prev_para
= NULL
;
562 if (para
->member
.para
.prev_para
->type
== diParagraph
)
563 prev_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
))
573 } else if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
574 pStream
->nNestingLevel
--;
575 if (pStream
->nNestingLevel
>= 1) {
576 if (!ME_StreamOutPrint(pStream
, "{\\*\\nesttableprops"))
578 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
580 if (!ME_StreamOutPrint(pStream
, "\\nestrow}{\\nonesttables\\par}\r\n"))
583 if (!ME_StreamOutPrint(pStream
, "\\row\r\n"))
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
))
597 if (prev_para
&& !memcmp( fmt
, &prev_para
->fmt
, sizeof(*fmt
) ))
600 return stream_out_para_num( pStream
, ¶
->member
.para
, FALSE
);
604 if (!ME_StreamOutPrint(pStream
, "\\pard"))
608 if (!stream_out_para_num( pStream
, ¶
->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
) {
628 /* Default alignment: not emitted */
631 strcat(props
, "\\qr");
634 strcat(props
, "\\qc");
637 strcat(props
, "\\qj");
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");
649 case 1: /* 1.5 spacing */
650 strcat(props
, "\\sl-360\\slmult1");
652 case 2: /* Double spacing */
653 strcat(props
, "\\sl-480\\slmult1");
656 sprintf(props
+ strlen(props
), "\\sl%d\\slmult0", fmt
->dyLineSpacing
);
659 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult0", fmt
->dyLineSpacing
);
662 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult1", fmt
->dyLineSpacing
* 240 / 20);
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
))
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) {
699 strcat(props
, "\\tqc");
702 strcat(props
, "\\tqr");
705 strcat(props
, "\\tqdec");
708 /* Word bar tab (vertical bar). Handled below */
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",
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);
740 if (*props
&& !ME_StreamOutPrint(pStream
, props
))
748 ME_StreamOutRTFCharProps(ME_OutStream
*pStream
, CHARFORMAT2W
*fmt
)
750 char props
[STREAMOUT_BUFFER_SIZE
] = "";
752 CHARFORMAT2W
*old_fmt
= &pStream
->cur_fmt
;
756 const char *on
, *off
;
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");
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
);
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");
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
;
835 strcat(props
, "\\ul");
837 case CFU_UNDERLINEDOTTED
:
838 strcat(props
, "\\uld");
840 case CFU_UNDERLINEDOUBLE
:
841 strcat(props
, "\\uldb");
843 case CFU_UNDERLINEWORD
:
844 strcat(props
, "\\ulw");
846 case CFU_CF1UNDERLINE
:
847 case CFU_UNDERLINENONE
:
849 strcat(props
, "\\ulnone");
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
;
867 pStream
->nCodePage
= RTFCharSetToCodePage(NULL
, pStream
->fonttbl
[i
].bCharSet
);
873 if (!ME_StreamOutPrint(pStream
, props
))
881 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
)
883 char buffer
[STREAMOUT_BUFFER_SIZE
];
888 nChars
= lstrlenW(text
);
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
);
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
))
906 if (!ME_StreamOutMove(pStream
, buffer
+ pos
, nBytes
- pos
))
909 } else if (*text
< 128) {
910 if (*text
== '{' || *text
== '}' || *text
== '\\')
911 buffer
[pos
++] = '\\';
912 buffer
[pos
++] = (char)(*text
++);
915 BOOL unknown
= FALSE
;
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
922 nBytes
= WideCharToMultiByte(pStream
->nCodePage
, 0, text
, 1,
924 (pStream
->nCodePage
== CP_SYMBOL
) ? NULL
: &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
;
932 for (i
= 0; i
< nBytes
; i
++)
933 pos
+= sprintf(buffer
+ pos
, "\\'%02x", (BYTE
)letter
[i
]);
938 if (pos
>= STREAMOUT_BUFFER_SIZE
- 11) {
939 if (!ME_StreamOutMove(pStream
, buffer
, pos
))
944 return ME_StreamOutMove(pStream
, buffer
, pos
);
947 static BOOL
stream_out_graphics( ME_TextEditor
*editor
, ME_OutStream
*stream
,
952 FORMATETC fmt
= { CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
};
953 STGMEDIUM med
= { TYMED_NULL
};
955 ENHMETAHEADER
*emf_bits
= NULL
;
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
))
995 if (!ME_StreamOutHexData( stream
, (BYTE
*)emf_bits
, size
))
998 if (!ME_StreamOutPrint( stream
, "}}\n" ))
1004 ME_DestroyContext( &c
);
1005 HeapFree( GetProcessHeap(), 0, emf_bits
);
1006 ReleaseStgMedium( &med
);
1007 IDataObject_Release( data
);
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
))
1023 if (!ME_StreamOutRTFFontAndColorTbl(pStream
, cursor
.pRun
, endCur
.pRun
))
1026 /* TODO: stylesheet table */
1028 if (!ME_StreamOutPrint(pStream
, "{\\*\\generator Wine Riched20 2.0;}"))
1031 /* TODO: information group */
1033 /* TODO: document formatting properties */
1035 /* FIXME: We have only one document section */
1037 /* TODO: section formatting properties */
1040 if (cursor
.pPara
!= prev_para
)
1042 prev_para
= cursor
.pPara
;
1043 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
1047 if (cursor
.pRun
== endCur
.pRun
&& !endCur
.nOffset
)
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
))
1053 if (cursor
.pRun
->member
.run
.nFlags
& MERF_GRAPHICS
) {
1054 if (!stream_out_graphics(editor
, pStream
, &cursor
.pRun
->member
.run
))
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 "))
1064 if (!ME_StreamOutPrint(pStream
, "\\tab "))
1067 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDCELL
) {
1068 if (pStream
->nNestingLevel
> 1) {
1069 if (!ME_StreamOutPrint(pStream
, "\\nestcell "))
1072 if (!ME_StreamOutPrint(pStream
, "\\cell "))
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"))
1084 if (!ME_StreamOutPrint(pStream
, "\\par\r\n"))
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"))
1096 TRACE("style %p\n", cursor
.pRun
->member
.run
.style
);
1097 if (!ME_StreamOutRTFCharProps(pStream
, &cursor
.pRun
->member
.run
.style
->fmt
))
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
))
1106 } while (cursor
.pRun
!= endCur
.pRun
&& ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
));
1108 if (!ME_StreamOutMove(pStream
, "}\0", 2))
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
;
1119 UINT nCodePage
= CP_ACP
;
1120 char *buffer
= NULL
;
1122 BOOL success
= TRUE
;
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
));
1143 success
= ME_StreamOutMove(pStream
, "\r\n", 2);
1145 if (dwFormat
& SF_UNICODE
)
1146 success
= ME_StreamOutMove(pStream
, (const char *)(get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
)),
1147 sizeof(WCHAR
) * nLen
);
1151 nSize
= WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
1152 nLen
, NULL
, 0, NULL
, NULL
);
1153 if (nSize
> nBufLen
) {
1155 buffer
= ALLOC_N_OBJ(char, 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
);
1166 cursor
.pRun
= ME_FindItemFwd(cursor
.pRun
, diRun
);
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
);
1190 ME_StreamOut(ME_TextEditor
*editor
, DWORD dwFormat
, EDITSTREAM
*stream
)
1195 if (dwFormat
& SFF_SELECTION
) {
1197 start
= editor
->pCursors
[ME_GetSelectionOfs(editor
, &nStart
, &nTo
)];
1198 nChars
= nTo
- nStart
;
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
)
1206 return ME_StreamOutRange(editor
, dwFormat
, &start
, nChars
, stream
);