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"
27 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
31 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
);
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;
42 pStream
->nFontTblLen
= 0;
43 pStream
->nColorTblLen
= 1;
44 pStream
->nNestingLevel
= 0;
50 ME_StreamOutFlush(ME_OutStream
*pStream
)
53 EDITSTREAM
*stream
= pStream
->stream
;
56 TRACE("sending %u bytes\n", pStream
->pos
);
57 nWritten
= pStream
->pos
;
58 stream
->dwError
= stream
->pfnCallback(stream
->dwCookie
, (LPBYTE
)pStream
->buffer
,
59 pStream
->pos
, &nWritten
);
60 TRACE("error=%u written=%u\n", stream
->dwError
, nWritten
);
61 if (nWritten
== 0 || stream
->dwError
)
63 /* Don't resend partial chunks if nWritten < pStream->pos */
71 ME_StreamOutFree(ME_OutStream
*pStream
)
73 LONG written
= pStream
->written
;
74 TRACE("total length = %u\n", written
);
82 ME_StreamOutMove(ME_OutStream
*pStream
, const char *buffer
, int len
)
85 int space
= STREAMOUT_BUFFER_SIZE
- pStream
->pos
;
86 int fit
= min(space
, len
);
88 TRACE("%u:%u:%s\n", pStream
->pos
, fit
, debugstr_an(buffer
,fit
));
89 memmove(pStream
->buffer
+ pStream
->pos
, buffer
, fit
);
93 if (pStream
->pos
== STREAMOUT_BUFFER_SIZE
) {
94 if (!ME_StreamOutFlush(pStream
))
103 ME_StreamOutPrint(ME_OutStream
*pStream
, const char *format
, ...)
105 char string
[STREAMOUT_BUFFER_SIZE
]; /* This is going to be enough */
109 va_start(valist
, format
);
110 len
= vsnprintf(string
, sizeof(string
), format
, valist
);
113 return ME_StreamOutMove(pStream
, string
, len
);
118 ME_StreamOutRTFHeader(ME_OutStream
*pStream
, int dwFormat
)
120 const char *cCharSet
= NULL
;
125 if (dwFormat
& SF_USECODEPAGE
) {
128 switch (HIWORD(dwFormat
)) {
131 nCodePage
= GetACP();
134 nCodePage
= GetOEMCP();
135 if (nCodePage
== 437)
137 else if (nCodePage
== 850)
146 if (HIWORD(dwFormat
) == CP_MACCP
) {
148 nCodePage
= 10000; /* MacRoman */
151 nCodePage
= 1252; /* Latin-1 */
153 if (GetCPInfoExW(HIWORD(dwFormat
), 0, &info
))
154 nCodePage
= info
.CodePage
;
158 /* TODO: If the original document contained an \ansicpg value, retain it.
159 * Otherwise, M$ richedit emits a codepage number determined from the
160 * charset of the default font here. Anyway, this value is not used by
162 nCodePage
= GetACP();
164 if (nCodePage
== CP_UTF8
)
165 success
= ME_StreamOutPrint(pStream
, "{\\urtf");
167 success
= ME_StreamOutPrint(pStream
, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet
, nCodePage
);
172 pStream
->nDefaultCodePage
= nCodePage
;
174 /* FIXME: This should be a document property */
175 /* TODO: handle SFF_PLAINRTF */
176 language
= GetUserDefaultLangID();
177 if (!ME_StreamOutPrint(pStream
, "\\deff0\\deflang%u\\deflangfe%u", language
, language
))
180 /* FIXME: This should be a document property */
181 pStream
->nDefaultFont
= 0;
188 ME_StreamOutRTFFontAndColorTbl(ME_OutStream
*pStream
, ME_DisplayItem
*pFirstRun
,
189 ME_DisplayItem
*pLastRun
)
191 ME_DisplayItem
*item
= pFirstRun
;
192 ME_FontTableItem
*table
= pStream
->fonttbl
;
194 ME_DisplayItem
*pLastPara
= ME_GetParagraph(pLastRun
);
195 ME_DisplayItem
*pCell
= NULL
;
198 CHARFORMAT2W
*fmt
= &item
->member
.run
.style
->fmt
;
201 if (fmt
->dwMask
& CFM_FACE
) {
202 WCHAR
*face
= fmt
->szFaceName
;
203 BYTE bCharSet
= (fmt
->dwMask
& CFM_CHARSET
) ? fmt
->bCharSet
: DEFAULT_CHARSET
;
205 for (i
= 0; i
< pStream
->nFontTblLen
; i
++)
206 if (table
[i
].bCharSet
== bCharSet
207 && (table
[i
].szFaceName
== face
|| !lstrcmpW(table
[i
].szFaceName
, face
)))
209 if (i
== pStream
->nFontTblLen
&& i
< STREAMOUT_FONTTBL_SIZE
) {
210 table
[i
].bCharSet
= bCharSet
;
211 table
[i
].szFaceName
= face
;
212 pStream
->nFontTblLen
++;
216 if (fmt
->dwMask
& CFM_COLOR
&& !(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
217 crColor
= fmt
->crTextColor
;
218 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
219 if (pStream
->colortbl
[i
] == crColor
)
221 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
222 pStream
->colortbl
[i
] = crColor
;
223 pStream
->nColorTblLen
++;
226 if (fmt
->dwMask
& CFM_BACKCOLOR
&& !(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
227 crColor
= fmt
->crBackColor
;
228 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
229 if (pStream
->colortbl
[i
] == crColor
)
231 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
232 pStream
->colortbl
[i
] = crColor
;
233 pStream
->nColorTblLen
++;
237 if (item
== pLastRun
)
239 item
= ME_FindItemFwd(item
, diRun
);
241 item
= ME_GetParagraph(pFirstRun
);
243 if ((pCell
= item
->member
.para
.pCell
))
245 ME_Border
* borders
[4] = { &pCell
->member
.cell
.border
.top
,
246 &pCell
->member
.cell
.border
.left
,
247 &pCell
->member
.cell
.border
.bottom
,
248 &pCell
->member
.cell
.border
.right
};
249 for (i
= 0; i
< 4; i
++)
251 if (borders
[i
]->width
> 0)
254 COLORREF crColor
= borders
[i
]->colorRef
;
255 for (j
= 1; j
< pStream
->nColorTblLen
; j
++)
256 if (pStream
->colortbl
[j
] == crColor
)
258 if (j
== pStream
->nColorTblLen
&& j
< STREAMOUT_COLORTBL_SIZE
) {
259 pStream
->colortbl
[j
] = crColor
;
260 pStream
->nColorTblLen
++;
265 if (item
== pLastPara
)
267 item
= item
->member
.para
.next_para
;
270 if (!ME_StreamOutPrint(pStream
, "{\\fonttbl"))
273 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
274 if (table
[i
].bCharSet
!= DEFAULT_CHARSET
) {
275 if (!ME_StreamOutPrint(pStream
, "{\\f%u\\fcharset%u ", i
, table
[i
].bCharSet
))
278 if (!ME_StreamOutPrint(pStream
, "{\\f%u ", i
))
281 if (!ME_StreamOutRTFText(pStream
, table
[i
].szFaceName
, -1))
283 if (!ME_StreamOutPrint(pStream
, ";}"))
286 if (!ME_StreamOutPrint(pStream
, "}\r\n"))
289 /* It seems like Open Office ignores \deff0 tag at RTF-header.
290 As result it can't correctly parse text before first \fN tag,
291 so we can put \f0 immediately after font table. This forces
292 parser to use the same font, that \deff0 specifies.
293 It makes OOffice happy */
294 if (!ME_StreamOutPrint(pStream
, "\\f0"))
297 /* Output the color table */
298 if (!ME_StreamOutPrint(pStream
, "{\\colortbl;")) return FALSE
; /* first entry is auto-color */
299 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
301 if (!ME_StreamOutPrint(pStream
, "\\red%u\\green%u\\blue%u;", pStream
->colortbl
[i
] & 0xFF,
302 (pStream
->colortbl
[i
] >> 8) & 0xFF, (pStream
->colortbl
[i
] >> 16) & 0xFF))
305 if (!ME_StreamOutPrint(pStream
, "}")) return FALSE
;
311 ME_StreamOutRTFTableProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
312 ME_DisplayItem
*para
)
314 ME_DisplayItem
*cell
;
315 char props
[STREAMOUT_BUFFER_SIZE
] = "";
317 const char sideChar
[4] = {'t','l','b','r'};
319 if (!ME_StreamOutPrint(pStream
, "\\trowd"))
321 if (!editor
->bEmulateVersion10
) { /* v4.1 */
322 PARAFORMAT2
*pFmt
= ME_GetTableRowEnd(para
)->member
.para
.pFmt
;
323 para
= ME_GetTableRowStart(para
);
324 cell
= para
->member
.para
.next_para
->member
.para
.pCell
;
327 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
328 if (pFmt
->dxStartIndent
)
329 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
331 ME_Border
* borders
[4] = { &cell
->member
.cell
.border
.top
,
332 &cell
->member
.cell
.border
.left
,
333 &cell
->member
.cell
.border
.bottom
,
334 &cell
->member
.cell
.border
.right
};
335 for (i
= 0; i
< 4; i
++)
337 if (borders
[i
]->width
)
340 COLORREF crColor
= borders
[i
]->colorRef
;
341 sprintf(props
+ strlen(props
), "\\clbrdr%c", sideChar
[i
]);
342 sprintf(props
+ strlen(props
), "\\brdrs");
343 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
344 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
345 if (pStream
->colortbl
[j
] == crColor
) {
346 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
352 sprintf(props
+ strlen(props
), "\\cellx%d", cell
->member
.cell
.nRightBoundary
);
353 cell
= cell
->member
.cell
.next_cell
;
354 } while (cell
->member
.cell
.next_cell
);
355 } else { /* v1.0 - 3.0 */
356 const ME_Border
* borders
[4] = { ¶
->member
.para
.border
.top
,
357 ¶
->member
.para
.border
.left
,
358 ¶
->member
.para
.border
.bottom
,
359 ¶
->member
.para
.border
.right
};
360 PARAFORMAT2
*pFmt
= para
->member
.para
.pFmt
;
362 assert(!(para
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)));
364 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
365 if (pFmt
->dxStartIndent
)
366 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
367 for (i
= 0; i
< 4; i
++)
369 if (borders
[i
]->width
)
372 COLORREF crColor
= borders
[i
]->colorRef
;
373 sprintf(props
+ strlen(props
), "\\trbrdr%c", sideChar
[i
]);
374 sprintf(props
+ strlen(props
), "\\brdrs");
375 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
376 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
377 if (pStream
->colortbl
[j
] == crColor
) {
378 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
384 for (i
= 0; i
< pFmt
->cTabCount
; i
++)
386 sprintf(props
+ strlen(props
), "\\cellx%d", pFmt
->rgxTabs
[i
] & 0x00FFFFFF);
389 if (!ME_StreamOutPrint(pStream
, props
))
396 ME_StreamOutRTFParaProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
397 ME_DisplayItem
*para
)
399 PARAFORMAT2
*fmt
= para
->member
.para
.pFmt
;
400 char props
[STREAMOUT_BUFFER_SIZE
] = "";
403 if (!editor
->bEmulateVersion10
) { /* v4.1 */
404 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
) {
405 pStream
->nNestingLevel
++;
406 if (pStream
->nNestingLevel
== 1) {
407 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
411 } else if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
412 pStream
->nNestingLevel
--;
413 if (pStream
->nNestingLevel
>= 1) {
414 if (!ME_StreamOutPrint(pStream
, "{\\*\\nesttableprops"))
416 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
418 if (!ME_StreamOutPrint(pStream
, "\\nestrow}{\\nonesttables\\par}\r\n"))
421 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
426 } else { /* v1.0 - 3.0 */
427 if (para
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
428 para
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
430 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
435 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
436 if (!ME_StreamOutPrint(pStream
, "\\pard"))
439 if (!editor
->bEmulateVersion10
) { /* v4.1 */
440 if (pStream
->nNestingLevel
> 0)
441 strcat(props
, "\\intbl");
442 if (pStream
->nNestingLevel
> 1)
443 sprintf(props
+ strlen(props
), "\\itap%d", pStream
->nNestingLevel
);
444 } else { /* v1.0 - 3.0 */
445 if (fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
)
446 strcat(props
, "\\intbl");
449 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
450 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
451 * set very different from the documentation.
452 * (Tested with RichEdit 5.50.25.0601) */
454 if (fmt
->dwMask
& PFM_ALIGNMENT
) {
455 switch (fmt
->wAlignment
) {
457 /* Default alignment: not emitted */
460 strcat(props
, "\\qr");
463 strcat(props
, "\\qc");
466 strcat(props
, "\\qj");
471 if (fmt
->dwMask
& PFM_LINESPACING
) {
472 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
473 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
474 switch (fmt
->bLineSpacingRule
) {
475 case 0: /* Single spacing */
476 strcat(props
, "\\sl-240\\slmult1");
478 case 1: /* 1.5 spacing */
479 strcat(props
, "\\sl-360\\slmult1");
481 case 2: /* Double spacing */
482 strcat(props
, "\\sl-480\\slmult1");
485 sprintf(props
+ strlen(props
), "\\sl%d\\slmult0", fmt
->dyLineSpacing
);
488 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult0", fmt
->dyLineSpacing
);
491 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult1", fmt
->dyLineSpacing
* 240 / 20);
496 if (fmt
->dwMask
& PFM_DONOTHYPHEN
&& fmt
->wEffects
& PFE_DONOTHYPHEN
)
497 strcat(props
, "\\hyph0");
498 if (fmt
->dwMask
& PFM_KEEP
&& fmt
->wEffects
& PFE_KEEP
)
499 strcat(props
, "\\keep");
500 if (fmt
->dwMask
& PFM_KEEPNEXT
&& fmt
->wEffects
& PFE_KEEPNEXT
)
501 strcat(props
, "\\keepn");
502 if (fmt
->dwMask
& PFM_NOLINENUMBER
&& fmt
->wEffects
& PFE_NOLINENUMBER
)
503 strcat(props
, "\\noline");
504 if (fmt
->dwMask
& PFM_NOWIDOWCONTROL
&& fmt
->wEffects
& PFE_NOWIDOWCONTROL
)
505 strcat(props
, "\\nowidctlpar");
506 if (fmt
->dwMask
& PFM_PAGEBREAKBEFORE
&& fmt
->wEffects
& PFE_PAGEBREAKBEFORE
)
507 strcat(props
, "\\pagebb");
508 if (fmt
->dwMask
& PFM_RTLPARA
&& fmt
->wEffects
& PFE_RTLPARA
)
509 strcat(props
, "\\rtlpar");
510 if (fmt
->dwMask
& PFM_SIDEBYSIDE
&& fmt
->wEffects
& PFE_SIDEBYSIDE
)
511 strcat(props
, "\\sbys");
513 if (!(editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
514 fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
))
516 if (fmt
->dwMask
& PFM_OFFSET
)
517 sprintf(props
+ strlen(props
), "\\li%d", fmt
->dxOffset
);
518 if (fmt
->dwMask
& PFM_OFFSETINDENT
|| fmt
->dwMask
& PFM_STARTINDENT
)
519 sprintf(props
+ strlen(props
), "\\fi%d", fmt
->dxStartIndent
);
520 if (fmt
->dwMask
& PFM_RIGHTINDENT
)
521 sprintf(props
+ strlen(props
), "\\ri%d", fmt
->dxRightIndent
);
522 if (fmt
->dwMask
& PFM_TABSTOPS
) {
523 static const char * const leader
[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
525 for (i
= 0; i
< fmt
->cTabCount
; i
++) {
526 switch ((fmt
->rgxTabs
[i
] >> 24) & 0xF) {
528 strcat(props
, "\\tqc");
531 strcat(props
, "\\tqr");
534 strcat(props
, "\\tqdec");
537 /* Word bar tab (vertical bar). Handled below */
540 if (fmt
->rgxTabs
[i
] >> 28 <= 5)
541 strcat(props
, leader
[fmt
->rgxTabs
[i
] >> 28]);
542 sprintf(props
+strlen(props
), "\\tx%d", fmt
->rgxTabs
[i
]&0x00FFFFFF);
546 if (fmt
->dwMask
& PFM_SPACEAFTER
)
547 sprintf(props
+ strlen(props
), "\\sa%d", fmt
->dySpaceAfter
);
548 if (fmt
->dwMask
& PFM_SPACEBEFORE
)
549 sprintf(props
+ strlen(props
), "\\sb%d", fmt
->dySpaceBefore
);
550 if (fmt
->dwMask
& PFM_STYLE
)
551 sprintf(props
+ strlen(props
), "\\s%d", fmt
->sStyle
);
553 if (fmt
->dwMask
& PFM_SHADING
) {
554 static const char * const style
[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
555 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
556 "\\bghoriz", "\\bgvert", "\\bgfdiag",
557 "\\bgbdiag", "\\bgcross", "\\bgdcross",
559 if (fmt
->wShadingWeight
)
560 sprintf(props
+ strlen(props
), "\\shading%d", fmt
->wShadingWeight
);
561 if (fmt
->wShadingStyle
& 0xF)
562 strcat(props
, style
[fmt
->wShadingStyle
& 0xF]);
563 sprintf(props
+ strlen(props
), "\\cfpat%d\\cbpat%d",
564 (fmt
->wShadingStyle
>> 4) & 0xF, (fmt
->wShadingStyle
>> 8) & 0xF);
567 if (*props
&& !ME_StreamOutPrint(pStream
, props
))
575 ME_StreamOutRTFCharProps(ME_OutStream
*pStream
, CHARFORMAT2W
*fmt
)
577 char props
[STREAMOUT_BUFFER_SIZE
] = "";
580 if (fmt
->dwMask
& CFM_ALLCAPS
&& fmt
->dwEffects
& CFE_ALLCAPS
)
581 strcat(props
, "\\caps");
582 if (fmt
->dwMask
& CFM_ANIMATION
)
583 sprintf(props
+ strlen(props
), "\\animtext%u", fmt
->bAnimation
);
584 if (fmt
->dwMask
& CFM_BACKCOLOR
) {
585 if (!(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
586 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
587 if (pStream
->colortbl
[i
] == fmt
->crBackColor
) {
588 sprintf(props
+ strlen(props
), "\\cb%u", i
);
593 if (fmt
->dwMask
& CFM_BOLD
&& fmt
->dwEffects
& CFE_BOLD
)
594 strcat(props
, "\\b");
595 if (fmt
->dwMask
& CFM_COLOR
) {
596 if (!(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
597 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
598 if (pStream
->colortbl
[i
] == fmt
->crTextColor
) {
599 sprintf(props
+ strlen(props
), "\\cf%u", i
);
604 /* TODO: CFM_DISABLED */
605 if (fmt
->dwMask
& CFM_EMBOSS
&& fmt
->dwEffects
& CFE_EMBOSS
)
606 strcat(props
, "\\embo");
607 if (fmt
->dwMask
& CFM_HIDDEN
&& fmt
->dwEffects
& CFE_HIDDEN
)
608 strcat(props
, "\\v");
609 if (fmt
->dwMask
& CFM_IMPRINT
&& fmt
->dwEffects
& CFE_IMPRINT
)
610 strcat(props
, "\\impr");
611 if (fmt
->dwMask
& CFM_ITALIC
&& fmt
->dwEffects
& CFE_ITALIC
)
612 strcat(props
, "\\i");
613 if (fmt
->dwMask
& CFM_KERNING
)
614 sprintf(props
+ strlen(props
), "\\kerning%u", fmt
->wKerning
);
615 if (fmt
->dwMask
& CFM_LCID
) {
616 /* TODO: handle SFF_PLAINRTF */
617 if (LOWORD(fmt
->lcid
) == 1024)
618 strcat(props
, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
620 sprintf(props
+ strlen(props
), "\\lang%u", LOWORD(fmt
->lcid
));
622 /* CFM_LINK is not streamed out by M$ */
623 if (fmt
->dwMask
& CFM_OFFSET
) {
624 if (fmt
->yOffset
>= 0)
625 sprintf(props
+ strlen(props
), "\\up%d", fmt
->yOffset
);
627 sprintf(props
+ strlen(props
), "\\dn%d", -fmt
->yOffset
);
629 if (fmt
->dwMask
& CFM_OUTLINE
&& fmt
->dwEffects
& CFE_OUTLINE
)
630 strcat(props
, "\\outl");
631 if (fmt
->dwMask
& CFM_PROTECTED
&& fmt
->dwEffects
& CFE_PROTECTED
)
632 strcat(props
, "\\protect");
633 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
634 if (fmt
->dwMask
& CFM_SHADOW
&& fmt
->dwEffects
& CFE_SHADOW
)
635 strcat(props
, "\\shad");
636 if (fmt
->dwMask
& CFM_SIZE
)
637 sprintf(props
+ strlen(props
), "\\fs%d", fmt
->yHeight
/ 10);
638 if (fmt
->dwMask
& CFM_SMALLCAPS
&& fmt
->dwEffects
& CFE_SMALLCAPS
)
639 strcat(props
, "\\scaps");
640 if (fmt
->dwMask
& CFM_SPACING
)
641 sprintf(props
+ strlen(props
), "\\expnd%u\\expndtw%u", fmt
->sSpacing
/ 5, fmt
->sSpacing
);
642 if (fmt
->dwMask
& CFM_STRIKEOUT
&& fmt
->dwEffects
& CFE_STRIKEOUT
)
643 strcat(props
, "\\strike");
644 if (fmt
->dwMask
& CFM_STYLE
) {
645 sprintf(props
+ strlen(props
), "\\cs%u", fmt
->sStyle
);
646 /* TODO: emit style contents here */
648 if (fmt
->dwMask
& (CFM_SUBSCRIPT
| CFM_SUPERSCRIPT
)) {
649 if (fmt
->dwEffects
& CFE_SUBSCRIPT
)
650 strcat(props
, "\\sub");
651 else if (fmt
->dwEffects
& CFE_SUPERSCRIPT
)
652 strcat(props
, "\\super");
654 if (fmt
->dwMask
& CFM_UNDERLINE
|| fmt
->dwMask
& CFM_UNDERLINETYPE
) {
655 if (fmt
->dwMask
& CFM_UNDERLINETYPE
)
656 switch (fmt
->bUnderlineType
) {
657 case CFU_CF1UNDERLINE
:
659 strcat(props
, "\\ul");
661 case CFU_UNDERLINEDOTTED
:
662 strcat(props
, "\\uld");
664 case CFU_UNDERLINEDOUBLE
:
665 strcat(props
, "\\uldb");
667 case CFU_UNDERLINEWORD
:
668 strcat(props
, "\\ulw");
670 case CFU_UNDERLINENONE
:
672 strcat(props
, "\\ulnone");
675 else if (fmt
->dwEffects
& CFE_UNDERLINE
)
676 strcat(props
, "\\ul");
678 /* FIXME: How to emit CFM_WEIGHT? */
680 if (fmt
->dwMask
& CFM_FACE
|| fmt
->dwMask
& CFM_CHARSET
) {
683 if (fmt
->dwMask
& CFM_FACE
)
684 szFaceName
= fmt
->szFaceName
;
686 szFaceName
= pStream
->fonttbl
[0].szFaceName
;
687 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
688 if (szFaceName
== pStream
->fonttbl
[i
].szFaceName
689 || !lstrcmpW(szFaceName
, pStream
->fonttbl
[i
].szFaceName
))
690 if (!(fmt
->dwMask
& CFM_CHARSET
)
691 || fmt
->bCharSet
== pStream
->fonttbl
[i
].bCharSet
)
694 if (i
< pStream
->nFontTblLen
)
696 if (i
!= pStream
->nDefaultFont
)
697 sprintf(props
+ strlen(props
), "\\f%u", i
);
699 /* In UTF-8 mode, charsets/codepages are not used */
700 if (pStream
->nDefaultCodePage
!= CP_UTF8
)
702 if (pStream
->fonttbl
[i
].bCharSet
== DEFAULT_CHARSET
)
703 pStream
->nCodePage
= pStream
->nDefaultCodePage
;
705 pStream
->nCodePage
= RTFCharSetToCodePage(NULL
, pStream
->fonttbl
[i
].bCharSet
);
711 if (!ME_StreamOutPrint(pStream
, props
))
718 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
)
720 char buffer
[STREAMOUT_BUFFER_SIZE
];
725 nChars
= lstrlenW(text
);
728 /* In UTF-8 mode, font charsets are not used. */
729 if (pStream
->nDefaultCodePage
== CP_UTF8
) {
730 /* 6 is the maximum character length in UTF-8 */
731 fit
= min(nChars
, STREAMOUT_BUFFER_SIZE
/ 6);
732 nBytes
= WideCharToMultiByte(CP_UTF8
, 0, text
, fit
, buffer
,
733 STREAMOUT_BUFFER_SIZE
, NULL
, NULL
);
736 for (i
= 0; i
< nBytes
; i
++)
737 if (buffer
[i
] == '{' || buffer
[i
] == '}' || buffer
[i
] == '\\') {
738 if (!ME_StreamOutPrint(pStream
, "%.*s\\", i
- pos
, buffer
+ pos
))
743 if (!ME_StreamOutMove(pStream
, buffer
+ pos
, nBytes
- pos
))
746 } else if (*text
< 128) {
747 if (*text
== '{' || *text
== '}' || *text
== '\\')
748 buffer
[pos
++] = '\\';
749 buffer
[pos
++] = (char)(*text
++);
752 BOOL unknown
= FALSE
;
755 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
756 * codepages including CP_SYMBOL for which the last parameter must be set
757 * to NULL for the function to succeed. But in Wine we need to care only
759 nBytes
= WideCharToMultiByte(pStream
->nCodePage
, 0, text
, 1,
761 (pStream
->nCodePage
== CP_SYMBOL
) ? NULL
: &unknown
);
763 pos
+= sprintf(buffer
+ pos
, "\\u%d?", (short)*text
);
764 else if ((BYTE
)*letter
< 128) {
765 if (*letter
== '{' || *letter
== '}' || *letter
== '\\')
766 buffer
[pos
++] = '\\';
767 buffer
[pos
++] = *letter
;
769 for (i
= 0; i
< nBytes
; i
++)
770 pos
+= sprintf(buffer
+ pos
, "\\'%02x", (BYTE
)letter
[i
]);
775 if (pos
>= STREAMOUT_BUFFER_SIZE
- 11) {
776 if (!ME_StreamOutMove(pStream
, buffer
, pos
))
781 return ME_StreamOutMove(pStream
, buffer
, pos
);
785 static BOOL
ME_StreamOutRTF(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
786 const ME_Cursor
*start
, int nChars
, int dwFormat
)
788 ME_Cursor cursor
= *start
;
789 ME_DisplayItem
*prev_para
= cursor
.pPara
;
790 ME_Cursor endCur
= cursor
;
793 actual_chars
= ME_MoveCursorChars(editor
, &endCur
, nChars
);
794 /* Include the final \r which MoveCursorChars will ignore. */
795 if (actual_chars
!= nChars
) endCur
.nOffset
++;
797 if (!ME_StreamOutRTFHeader(pStream
, dwFormat
))
800 if (!ME_StreamOutRTFFontAndColorTbl(pStream
, cursor
.pRun
, endCur
.pRun
))
803 /* TODO: stylesheet table */
805 /* FIXME: maybe emit something smarter for the generator? */
806 if (!ME_StreamOutPrint(pStream
, "{\\*\\generator Wine Riched20 2.0.????;}"))
809 /* TODO: information group */
811 /* TODO: document formatting properties */
813 /* FIXME: We have only one document section */
815 /* TODO: section formatting properties */
817 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
821 if (cursor
.pPara
!= prev_para
)
823 prev_para
= cursor
.pPara
;
824 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
828 if (cursor
.pRun
== endCur
.pRun
&& !endCur
.nOffset
)
830 TRACE("flags %xh\n", cursor
.pRun
->member
.run
.nFlags
);
831 /* TODO: emit embedded objects */
832 if (cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
))
834 if (cursor
.pRun
->member
.run
.nFlags
& MERF_GRAPHICS
) {
835 FIXME("embedded objects are not handled\n");
836 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_TAB
) {
837 if (editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
838 cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
839 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
841 if (!ME_StreamOutPrint(pStream
, "\\cell "))
844 if (!ME_StreamOutPrint(pStream
, "\\tab "))
847 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDCELL
) {
848 if (pStream
->nNestingLevel
> 1) {
849 if (!ME_StreamOutPrint(pStream
, "\\nestcell "))
852 if (!ME_StreamOutPrint(pStream
, "\\cell "))
856 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
) {
857 if (cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
858 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
&&
859 !(cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)))
861 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
864 if (!ME_StreamOutPrint(pStream
, "\r\n\\par"))
867 /* Skip as many characters as required by current line break */
868 nChars
= max(0, nChars
- cursor
.pRun
->member
.run
.len
);
869 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDROW
) {
870 if (!ME_StreamOutPrint(pStream
, "\\line \r\n"))
876 if (!ME_StreamOutPrint(pStream
, "{"))
878 TRACE("style %p\n", cursor
.pRun
->member
.run
.style
);
879 if (!ME_StreamOutRTFCharProps(pStream
, &cursor
.pRun
->member
.run
.style
->fmt
))
882 nEnd
= (cursor
.pRun
== endCur
.pRun
) ? endCur
.nOffset
: cursor
.pRun
->member
.run
.len
;
883 if (!ME_StreamOutRTFText(pStream
, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
884 nEnd
- cursor
.nOffset
))
887 if (!ME_StreamOutPrint(pStream
, "}"))
890 } while (cursor
.pRun
!= endCur
.pRun
&& ME_NextRun(&cursor
.pPara
, &cursor
.pRun
));
892 if (!ME_StreamOutMove(pStream
, "}\0", 2))
898 static BOOL
ME_StreamOutText(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
899 const ME_Cursor
*start
, int nChars
, DWORD dwFormat
)
901 ME_Cursor cursor
= *start
;
903 UINT nCodePage
= CP_ACP
;
911 if (dwFormat
& SF_USECODEPAGE
)
912 nCodePage
= HIWORD(dwFormat
);
914 /* TODO: Handle SF_TEXTIZED */
916 while (success
&& nChars
&& cursor
.pRun
) {
917 nLen
= min(nChars
, cursor
.pRun
->member
.run
.len
- cursor
.nOffset
);
919 if (!editor
->bEmulateVersion10
&& cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
921 static const WCHAR szEOL
[] = { '\r', '\n' };
923 /* richedit 2.0 - all line breaks are \r\n */
924 if (dwFormat
& SF_UNICODE
)
925 success
= ME_StreamOutMove(pStream
, (const char *)szEOL
, sizeof(szEOL
));
927 success
= ME_StreamOutMove(pStream
, "\r\n", 2);
929 if (dwFormat
& SF_UNICODE
)
930 success
= ME_StreamOutMove(pStream
, (const char *)(get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
)),
931 sizeof(WCHAR
) * nLen
);
935 nSize
= WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
936 nLen
, NULL
, 0, NULL
, NULL
);
937 if (nSize
> nBufLen
) {
939 buffer
= ALLOC_N_OBJ(char, nSize
);
942 WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
943 nLen
, buffer
, nSize
, NULL
, NULL
);
944 success
= ME_StreamOutMove(pStream
, buffer
, nSize
);
950 cursor
.pRun
= ME_FindItemFwd(cursor
.pRun
, diRun
);
958 LRESULT
ME_StreamOutRange(ME_TextEditor
*editor
, DWORD dwFormat
,
959 const ME_Cursor
*start
,
960 int nChars
, EDITSTREAM
*stream
)
962 ME_OutStream
*pStream
= ME_StreamOutInit(editor
, stream
);
964 if (dwFormat
& SF_RTF
)
965 ME_StreamOutRTF(editor
, pStream
, start
, nChars
, dwFormat
);
966 else if (dwFormat
& SF_TEXT
|| dwFormat
& SF_TEXTIZED
)
967 ME_StreamOutText(editor
, pStream
, start
, nChars
, dwFormat
);
968 if (!pStream
->stream
->dwError
)
969 ME_StreamOutFlush(pStream
);
970 return ME_StreamOutFree(pStream
);
974 ME_StreamOut(ME_TextEditor
*editor
, DWORD dwFormat
, EDITSTREAM
*stream
)
979 if (dwFormat
& SFF_SELECTION
) {
981 start
= editor
->pCursors
[ME_GetSelectionOfs(editor
, &nStart
, &nTo
)];
982 nChars
= nTo
- nStart
;
984 ME_SetCursorToStart(editor
, &start
);
985 nChars
= ME_GetTextLength(editor
);
986 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
987 if (dwFormat
& SF_RTF
)
990 return ME_StreamOutRange(editor
, dwFormat
, &start
, nChars
, stream
);