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 */
65 if (nWritten
== pStream
->pos
)
66 pStream
->written
+= nWritten
;
73 ME_StreamOutFree(ME_OutStream
*pStream
)
75 LONG written
= pStream
->written
;
76 TRACE("total length = %u\n", written
);
84 ME_StreamOutMove(ME_OutStream
*pStream
, const char *buffer
, int len
)
87 int space
= STREAMOUT_BUFFER_SIZE
- pStream
->pos
;
88 int fit
= min(space
, len
);
90 TRACE("%u:%u:%s\n", pStream
->pos
, fit
, debugstr_an(buffer
,fit
));
91 memmove(pStream
->buffer
+ pStream
->pos
, buffer
, fit
);
95 if (pStream
->pos
== STREAMOUT_BUFFER_SIZE
) {
96 if (!ME_StreamOutFlush(pStream
))
105 ME_StreamOutPrint(ME_OutStream
*pStream
, const char *format
, ...)
107 char string
[STREAMOUT_BUFFER_SIZE
]; /* This is going to be enough */
111 va_start(valist
, format
);
112 len
= vsnprintf(string
, sizeof(string
), format
, valist
);
115 return ME_StreamOutMove(pStream
, string
, len
);
120 ME_StreamOutRTFHeader(ME_OutStream
*pStream
, int dwFormat
)
122 const char *cCharSet
= NULL
;
127 if (dwFormat
& SF_USECODEPAGE
) {
130 switch (HIWORD(dwFormat
)) {
133 nCodePage
= GetACP();
136 nCodePage
= GetOEMCP();
137 if (nCodePage
== 437)
139 else if (nCodePage
== 850)
148 if (HIWORD(dwFormat
) == CP_MACCP
) {
150 nCodePage
= 10000; /* MacRoman */
153 nCodePage
= 1252; /* Latin-1 */
155 if (GetCPInfoExW(HIWORD(dwFormat
), 0, &info
))
156 nCodePage
= info
.CodePage
;
160 /* TODO: If the original document contained an \ansicpg value, retain it.
161 * Otherwise, M$ richedit emits a codepage number determined from the
162 * charset of the default font here. Anyway, this value is not used by
164 nCodePage
= GetACP();
166 if (nCodePage
== CP_UTF8
)
167 success
= ME_StreamOutPrint(pStream
, "{\\urtf");
169 success
= ME_StreamOutPrint(pStream
, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet
, nCodePage
);
174 pStream
->nDefaultCodePage
= nCodePage
;
176 /* FIXME: This should be a document property */
177 /* TODO: handle SFF_PLAINRTF */
178 language
= GetUserDefaultLangID();
179 if (!ME_StreamOutPrint(pStream
, "\\deff0\\deflang%u\\deflangfe%u", language
, language
))
182 /* FIXME: This should be a document property */
183 pStream
->nDefaultFont
= 0;
190 ME_StreamOutRTFFontAndColorTbl(ME_OutStream
*pStream
, ME_DisplayItem
*pFirstRun
,
191 ME_DisplayItem
*pLastRun
)
193 ME_DisplayItem
*item
= pFirstRun
;
194 ME_FontTableItem
*table
= pStream
->fonttbl
;
196 ME_DisplayItem
*pLastPara
= ME_GetParagraph(pLastRun
);
197 ME_DisplayItem
*pCell
= NULL
;
200 CHARFORMAT2W
*fmt
= &item
->member
.run
.style
->fmt
;
203 if (fmt
->dwMask
& CFM_FACE
) {
204 WCHAR
*face
= fmt
->szFaceName
;
205 BYTE bCharSet
= (fmt
->dwMask
& CFM_CHARSET
) ? fmt
->bCharSet
: DEFAULT_CHARSET
;
207 for (i
= 0; i
< pStream
->nFontTblLen
; i
++)
208 if (table
[i
].bCharSet
== bCharSet
209 && (table
[i
].szFaceName
== face
|| !lstrcmpW(table
[i
].szFaceName
, face
)))
211 if (i
== pStream
->nFontTblLen
&& i
< STREAMOUT_FONTTBL_SIZE
) {
212 table
[i
].bCharSet
= bCharSet
;
213 table
[i
].szFaceName
= face
;
214 pStream
->nFontTblLen
++;
218 if (fmt
->dwMask
& CFM_COLOR
&& !(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
219 crColor
= fmt
->crTextColor
;
220 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
221 if (pStream
->colortbl
[i
] == crColor
)
223 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
224 pStream
->colortbl
[i
] = crColor
;
225 pStream
->nColorTblLen
++;
228 if (fmt
->dwMask
& CFM_BACKCOLOR
&& !(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
229 crColor
= fmt
->crBackColor
;
230 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
231 if (pStream
->colortbl
[i
] == crColor
)
233 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
234 pStream
->colortbl
[i
] = crColor
;
235 pStream
->nColorTblLen
++;
239 if (item
== pLastRun
)
241 item
= ME_FindItemFwd(item
, diRun
);
243 item
= ME_GetParagraph(pFirstRun
);
245 if ((pCell
= item
->member
.para
.pCell
))
247 ME_Border
* borders
[4] = { &pCell
->member
.cell
.border
.top
,
248 &pCell
->member
.cell
.border
.left
,
249 &pCell
->member
.cell
.border
.bottom
,
250 &pCell
->member
.cell
.border
.right
};
251 for (i
= 0; i
< 4; i
++)
253 if (borders
[i
]->width
> 0)
256 COLORREF crColor
= borders
[i
]->colorRef
;
257 for (j
= 1; j
< pStream
->nColorTblLen
; j
++)
258 if (pStream
->colortbl
[j
] == crColor
)
260 if (j
== pStream
->nColorTblLen
&& j
< STREAMOUT_COLORTBL_SIZE
) {
261 pStream
->colortbl
[j
] = crColor
;
262 pStream
->nColorTblLen
++;
267 if (item
== pLastPara
)
269 item
= item
->member
.para
.next_para
;
272 if (!ME_StreamOutPrint(pStream
, "{\\fonttbl"))
275 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
276 if (table
[i
].bCharSet
!= DEFAULT_CHARSET
) {
277 if (!ME_StreamOutPrint(pStream
, "{\\f%u\\fcharset%u ", i
, table
[i
].bCharSet
))
280 if (!ME_StreamOutPrint(pStream
, "{\\f%u ", i
))
283 if (!ME_StreamOutRTFText(pStream
, table
[i
].szFaceName
, -1))
285 if (!ME_StreamOutPrint(pStream
, ";}"))
288 if (!ME_StreamOutPrint(pStream
, "}\r\n"))
291 /* It seems like Open Office ignores \deff0 tag at RTF-header.
292 As result it can't correctly parse text before first \fN tag,
293 so we can put \f0 immediately after font table. This forces
294 parser to use the same font, that \deff0 specifies.
295 It makes OOffice happy */
296 if (!ME_StreamOutPrint(pStream
, "\\f0"))
299 /* Output the color table */
300 if (!ME_StreamOutPrint(pStream
, "{\\colortbl;")) return FALSE
; /* first entry is auto-color */
301 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
303 if (!ME_StreamOutPrint(pStream
, "\\red%u\\green%u\\blue%u;", pStream
->colortbl
[i
] & 0xFF,
304 (pStream
->colortbl
[i
] >> 8) & 0xFF, (pStream
->colortbl
[i
] >> 16) & 0xFF))
307 if (!ME_StreamOutPrint(pStream
, "}")) return FALSE
;
313 ME_StreamOutRTFTableProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
314 ME_DisplayItem
*para
)
316 ME_DisplayItem
*cell
;
317 char props
[STREAMOUT_BUFFER_SIZE
] = "";
319 const char sideChar
[4] = {'t','l','b','r'};
321 if (!ME_StreamOutPrint(pStream
, "\\trowd"))
323 if (!editor
->bEmulateVersion10
) { /* v4.1 */
324 PARAFORMAT2
*pFmt
= ME_GetTableRowEnd(para
)->member
.para
.pFmt
;
325 para
= ME_GetTableRowStart(para
);
326 cell
= para
->member
.para
.next_para
->member
.para
.pCell
;
329 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
330 if (pFmt
->dxStartIndent
)
331 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
333 ME_Border
* borders
[4] = { &cell
->member
.cell
.border
.top
,
334 &cell
->member
.cell
.border
.left
,
335 &cell
->member
.cell
.border
.bottom
,
336 &cell
->member
.cell
.border
.right
};
337 for (i
= 0; i
< 4; i
++)
339 if (borders
[i
]->width
)
342 COLORREF crColor
= borders
[i
]->colorRef
;
343 sprintf(props
+ strlen(props
), "\\clbrdr%c", sideChar
[i
]);
344 sprintf(props
+ strlen(props
), "\\brdrs");
345 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
346 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
347 if (pStream
->colortbl
[j
] == crColor
) {
348 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
354 sprintf(props
+ strlen(props
), "\\cellx%d", cell
->member
.cell
.nRightBoundary
);
355 cell
= cell
->member
.cell
.next_cell
;
356 } while (cell
->member
.cell
.next_cell
);
357 } else { /* v1.0 - 3.0 */
358 const ME_Border
* borders
[4] = { ¶
->member
.para
.border
.top
,
359 ¶
->member
.para
.border
.left
,
360 ¶
->member
.para
.border
.bottom
,
361 ¶
->member
.para
.border
.right
};
362 PARAFORMAT2
*pFmt
= para
->member
.para
.pFmt
;
364 assert(!(para
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)));
366 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
367 if (pFmt
->dxStartIndent
)
368 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
369 for (i
= 0; i
< 4; i
++)
371 if (borders
[i
]->width
)
374 COLORREF crColor
= borders
[i
]->colorRef
;
375 sprintf(props
+ strlen(props
), "\\trbrdr%c", sideChar
[i
]);
376 sprintf(props
+ strlen(props
), "\\brdrs");
377 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
378 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
379 if (pStream
->colortbl
[j
] == crColor
) {
380 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
386 for (i
= 0; i
< pFmt
->cTabCount
; i
++)
388 sprintf(props
+ strlen(props
), "\\cellx%d", pFmt
->rgxTabs
[i
] & 0x00FFFFFF);
391 if (!ME_StreamOutPrint(pStream
, props
))
398 ME_StreamOutRTFParaProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
399 ME_DisplayItem
*para
)
401 PARAFORMAT2
*fmt
= para
->member
.para
.pFmt
;
402 char props
[STREAMOUT_BUFFER_SIZE
] = "";
405 if (!editor
->bEmulateVersion10
) { /* v4.1 */
406 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
) {
407 pStream
->nNestingLevel
++;
408 if (pStream
->nNestingLevel
== 1) {
409 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
413 } else if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
414 pStream
->nNestingLevel
--;
415 if (pStream
->nNestingLevel
>= 1) {
416 if (!ME_StreamOutPrint(pStream
, "{\\*\\nesttableprops"))
418 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
420 if (!ME_StreamOutPrint(pStream
, "\\nestrow}{\\nonesttables\\par}\r\n"))
423 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
428 } else { /* v1.0 - 3.0 */
429 if (para
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
430 para
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
432 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
437 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
438 if (!ME_StreamOutPrint(pStream
, "\\pard"))
441 if (!editor
->bEmulateVersion10
) { /* v4.1 */
442 if (pStream
->nNestingLevel
> 0)
443 strcat(props
, "\\intbl");
444 if (pStream
->nNestingLevel
> 1)
445 sprintf(props
+ strlen(props
), "\\itap%d", pStream
->nNestingLevel
);
446 } else { /* v1.0 - 3.0 */
447 if (fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
)
448 strcat(props
, "\\intbl");
451 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
452 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
453 * set very different from the documentation.
454 * (Tested with RichEdit 5.50.25.0601) */
456 if (fmt
->dwMask
& PFM_ALIGNMENT
) {
457 switch (fmt
->wAlignment
) {
459 /* Default alignment: not emitted */
462 strcat(props
, "\\qr");
465 strcat(props
, "\\qc");
468 strcat(props
, "\\qj");
473 if (fmt
->dwMask
& PFM_LINESPACING
) {
474 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
475 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
476 switch (fmt
->bLineSpacingRule
) {
477 case 0: /* Single spacing */
478 strcat(props
, "\\sl-240\\slmult1");
480 case 1: /* 1.5 spacing */
481 strcat(props
, "\\sl-360\\slmult1");
483 case 2: /* Double spacing */
484 strcat(props
, "\\sl-480\\slmult1");
487 sprintf(props
+ strlen(props
), "\\sl%d\\slmult0", fmt
->dyLineSpacing
);
490 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult0", fmt
->dyLineSpacing
);
493 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult1", fmt
->dyLineSpacing
* 240 / 20);
498 if (fmt
->dwMask
& PFM_DONOTHYPHEN
&& fmt
->wEffects
& PFE_DONOTHYPHEN
)
499 strcat(props
, "\\hyph0");
500 if (fmt
->dwMask
& PFM_KEEP
&& fmt
->wEffects
& PFE_KEEP
)
501 strcat(props
, "\\keep");
502 if (fmt
->dwMask
& PFM_KEEPNEXT
&& fmt
->wEffects
& PFE_KEEPNEXT
)
503 strcat(props
, "\\keepn");
504 if (fmt
->dwMask
& PFM_NOLINENUMBER
&& fmt
->wEffects
& PFE_NOLINENUMBER
)
505 strcat(props
, "\\noline");
506 if (fmt
->dwMask
& PFM_NOWIDOWCONTROL
&& fmt
->wEffects
& PFE_NOWIDOWCONTROL
)
507 strcat(props
, "\\nowidctlpar");
508 if (fmt
->dwMask
& PFM_PAGEBREAKBEFORE
&& fmt
->wEffects
& PFE_PAGEBREAKBEFORE
)
509 strcat(props
, "\\pagebb");
510 if (fmt
->dwMask
& PFM_RTLPARA
&& fmt
->wEffects
& PFE_RTLPARA
)
511 strcat(props
, "\\rtlpar");
512 if (fmt
->dwMask
& PFM_SIDEBYSIDE
&& fmt
->wEffects
& PFE_SIDEBYSIDE
)
513 strcat(props
, "\\sbys");
515 if (!(editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
516 fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
))
518 if (fmt
->dwMask
& PFM_OFFSET
)
519 sprintf(props
+ strlen(props
), "\\li%d", fmt
->dxOffset
);
520 if (fmt
->dwMask
& PFM_OFFSETINDENT
|| fmt
->dwMask
& PFM_STARTINDENT
)
521 sprintf(props
+ strlen(props
), "\\fi%d", fmt
->dxStartIndent
);
522 if (fmt
->dwMask
& PFM_RIGHTINDENT
)
523 sprintf(props
+ strlen(props
), "\\ri%d", fmt
->dxRightIndent
);
524 if (fmt
->dwMask
& PFM_TABSTOPS
) {
525 static const char * const leader
[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
527 for (i
= 0; i
< fmt
->cTabCount
; i
++) {
528 switch ((fmt
->rgxTabs
[i
] >> 24) & 0xF) {
530 strcat(props
, "\\tqc");
533 strcat(props
, "\\tqr");
536 strcat(props
, "\\tqdec");
539 /* Word bar tab (vertical bar). Handled below */
542 if (fmt
->rgxTabs
[i
] >> 28 <= 5)
543 strcat(props
, leader
[fmt
->rgxTabs
[i
] >> 28]);
544 sprintf(props
+strlen(props
), "\\tx%d", fmt
->rgxTabs
[i
]&0x00FFFFFF);
548 if (fmt
->dwMask
& PFM_SPACEAFTER
)
549 sprintf(props
+ strlen(props
), "\\sa%d", fmt
->dySpaceAfter
);
550 if (fmt
->dwMask
& PFM_SPACEBEFORE
)
551 sprintf(props
+ strlen(props
), "\\sb%d", fmt
->dySpaceBefore
);
552 if (fmt
->dwMask
& PFM_STYLE
)
553 sprintf(props
+ strlen(props
), "\\s%d", fmt
->sStyle
);
555 if (fmt
->dwMask
& PFM_SHADING
) {
556 static const char * const style
[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
557 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
558 "\\bghoriz", "\\bgvert", "\\bgfdiag",
559 "\\bgbdiag", "\\bgcross", "\\bgdcross",
561 if (fmt
->wShadingWeight
)
562 sprintf(props
+ strlen(props
), "\\shading%d", fmt
->wShadingWeight
);
563 if (fmt
->wShadingStyle
& 0xF)
564 strcat(props
, style
[fmt
->wShadingStyle
& 0xF]);
565 sprintf(props
+ strlen(props
), "\\cfpat%d\\cbpat%d",
566 (fmt
->wShadingStyle
>> 4) & 0xF, (fmt
->wShadingStyle
>> 8) & 0xF);
569 if (*props
&& !ME_StreamOutPrint(pStream
, props
))
577 ME_StreamOutRTFCharProps(ME_OutStream
*pStream
, CHARFORMAT2W
*fmt
)
579 char props
[STREAMOUT_BUFFER_SIZE
] = "";
582 if (fmt
->dwMask
& CFM_ALLCAPS
&& fmt
->dwEffects
& CFE_ALLCAPS
)
583 strcat(props
, "\\caps");
584 if (fmt
->dwMask
& CFM_ANIMATION
)
585 sprintf(props
+ strlen(props
), "\\animtext%u", fmt
->bAnimation
);
586 if (fmt
->dwMask
& CFM_BACKCOLOR
) {
587 if (!(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
588 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
589 if (pStream
->colortbl
[i
] == fmt
->crBackColor
) {
590 sprintf(props
+ strlen(props
), "\\cb%u", i
);
595 if (fmt
->dwMask
& CFM_BOLD
&& fmt
->dwEffects
& CFE_BOLD
)
596 strcat(props
, "\\b");
597 if (fmt
->dwMask
& CFM_COLOR
) {
598 if (!(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
599 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
600 if (pStream
->colortbl
[i
] == fmt
->crTextColor
) {
601 sprintf(props
+ strlen(props
), "\\cf%u", i
);
606 /* TODO: CFM_DISABLED */
607 if (fmt
->dwMask
& CFM_EMBOSS
&& fmt
->dwEffects
& CFE_EMBOSS
)
608 strcat(props
, "\\embo");
609 if (fmt
->dwMask
& CFM_HIDDEN
&& fmt
->dwEffects
& CFE_HIDDEN
)
610 strcat(props
, "\\v");
611 if (fmt
->dwMask
& CFM_IMPRINT
&& fmt
->dwEffects
& CFE_IMPRINT
)
612 strcat(props
, "\\impr");
613 if (fmt
->dwMask
& CFM_ITALIC
&& fmt
->dwEffects
& CFE_ITALIC
)
614 strcat(props
, "\\i");
615 if (fmt
->dwMask
& CFM_KERNING
)
616 sprintf(props
+ strlen(props
), "\\kerning%u", fmt
->wKerning
);
617 if (fmt
->dwMask
& CFM_LCID
) {
618 /* TODO: handle SFF_PLAINRTF */
619 if (LOWORD(fmt
->lcid
) == 1024)
620 strcat(props
, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
622 sprintf(props
+ strlen(props
), "\\lang%u", LOWORD(fmt
->lcid
));
624 /* CFM_LINK is not streamed out by M$ */
625 if (fmt
->dwMask
& CFM_OFFSET
) {
626 if (fmt
->yOffset
>= 0)
627 sprintf(props
+ strlen(props
), "\\up%d", fmt
->yOffset
);
629 sprintf(props
+ strlen(props
), "\\dn%d", -fmt
->yOffset
);
631 if (fmt
->dwMask
& CFM_OUTLINE
&& fmt
->dwEffects
& CFE_OUTLINE
)
632 strcat(props
, "\\outl");
633 if (fmt
->dwMask
& CFM_PROTECTED
&& fmt
->dwEffects
& CFE_PROTECTED
)
634 strcat(props
, "\\protect");
635 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
636 if (fmt
->dwMask
& CFM_SHADOW
&& fmt
->dwEffects
& CFE_SHADOW
)
637 strcat(props
, "\\shad");
638 if (fmt
->dwMask
& CFM_SIZE
)
639 sprintf(props
+ strlen(props
), "\\fs%d", fmt
->yHeight
/ 10);
640 if (fmt
->dwMask
& CFM_SMALLCAPS
&& fmt
->dwEffects
& CFE_SMALLCAPS
)
641 strcat(props
, "\\scaps");
642 if (fmt
->dwMask
& CFM_SPACING
)
643 sprintf(props
+ strlen(props
), "\\expnd%u\\expndtw%u", fmt
->sSpacing
/ 5, fmt
->sSpacing
);
644 if (fmt
->dwMask
& CFM_STRIKEOUT
&& fmt
->dwEffects
& CFE_STRIKEOUT
)
645 strcat(props
, "\\strike");
646 if (fmt
->dwMask
& CFM_STYLE
) {
647 sprintf(props
+ strlen(props
), "\\cs%u", fmt
->sStyle
);
648 /* TODO: emit style contents here */
650 if (fmt
->dwMask
& (CFM_SUBSCRIPT
| CFM_SUPERSCRIPT
)) {
651 if (fmt
->dwEffects
& CFE_SUBSCRIPT
)
652 strcat(props
, "\\sub");
653 else if (fmt
->dwEffects
& CFE_SUPERSCRIPT
)
654 strcat(props
, "\\super");
656 if (fmt
->dwMask
& CFM_UNDERLINE
|| fmt
->dwMask
& CFM_UNDERLINETYPE
) {
657 if (fmt
->dwMask
& CFM_UNDERLINETYPE
)
658 switch (fmt
->bUnderlineType
) {
659 case CFU_CF1UNDERLINE
:
661 strcat(props
, "\\ul");
663 case CFU_UNDERLINEDOTTED
:
664 strcat(props
, "\\uld");
666 case CFU_UNDERLINEDOUBLE
:
667 strcat(props
, "\\uldb");
669 case CFU_UNDERLINEWORD
:
670 strcat(props
, "\\ulw");
672 case CFU_UNDERLINENONE
:
674 strcat(props
, "\\ulnone");
677 else if (fmt
->dwEffects
& CFE_UNDERLINE
)
678 strcat(props
, "\\ul");
680 /* FIXME: How to emit CFM_WEIGHT? */
682 if (fmt
->dwMask
& CFM_FACE
|| fmt
->dwMask
& CFM_CHARSET
) {
685 if (fmt
->dwMask
& CFM_FACE
)
686 szFaceName
= fmt
->szFaceName
;
688 szFaceName
= pStream
->fonttbl
[0].szFaceName
;
689 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
690 if (szFaceName
== pStream
->fonttbl
[i
].szFaceName
691 || !lstrcmpW(szFaceName
, pStream
->fonttbl
[i
].szFaceName
))
692 if (!(fmt
->dwMask
& CFM_CHARSET
)
693 || fmt
->bCharSet
== pStream
->fonttbl
[i
].bCharSet
)
696 if (i
< pStream
->nFontTblLen
)
698 if (i
!= pStream
->nDefaultFont
)
699 sprintf(props
+ strlen(props
), "\\f%u", i
);
701 /* In UTF-8 mode, charsets/codepages are not used */
702 if (pStream
->nDefaultCodePage
!= CP_UTF8
)
704 if (pStream
->fonttbl
[i
].bCharSet
== DEFAULT_CHARSET
)
705 pStream
->nCodePage
= pStream
->nDefaultCodePage
;
707 pStream
->nCodePage
= RTFCharSetToCodePage(NULL
, pStream
->fonttbl
[i
].bCharSet
);
713 if (!ME_StreamOutPrint(pStream
, props
))
720 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
)
722 char buffer
[STREAMOUT_BUFFER_SIZE
];
727 nChars
= lstrlenW(text
);
730 /* In UTF-8 mode, font charsets are not used. */
731 if (pStream
->nDefaultCodePage
== CP_UTF8
) {
732 /* 6 is the maximum character length in UTF-8 */
733 fit
= min(nChars
, STREAMOUT_BUFFER_SIZE
/ 6);
734 nBytes
= WideCharToMultiByte(CP_UTF8
, 0, text
, fit
, buffer
,
735 STREAMOUT_BUFFER_SIZE
, NULL
, NULL
);
738 for (i
= 0; i
< nBytes
; i
++)
739 if (buffer
[i
] == '{' || buffer
[i
] == '}' || buffer
[i
] == '\\') {
740 if (!ME_StreamOutPrint(pStream
, "%.*s\\", i
- pos
, buffer
+ pos
))
745 if (!ME_StreamOutMove(pStream
, buffer
+ pos
, nBytes
- pos
))
748 } else if (*text
< 128) {
749 if (*text
== '{' || *text
== '}' || *text
== '\\')
750 buffer
[pos
++] = '\\';
751 buffer
[pos
++] = (char)(*text
++);
754 BOOL unknown
= FALSE
;
757 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
758 * codepages including CP_SYMBOL for which the last parameter must be set
759 * to NULL for the function to succeed. But in Wine we need to care only
761 nBytes
= WideCharToMultiByte(pStream
->nCodePage
, 0, text
, 1,
763 (pStream
->nCodePage
== CP_SYMBOL
) ? NULL
: &unknown
);
765 pos
+= sprintf(buffer
+ pos
, "\\u%d?", (short)*text
);
766 else if ((BYTE
)*letter
< 128) {
767 if (*letter
== '{' || *letter
== '}' || *letter
== '\\')
768 buffer
[pos
++] = '\\';
769 buffer
[pos
++] = *letter
;
771 for (i
= 0; i
< nBytes
; i
++)
772 pos
+= sprintf(buffer
+ pos
, "\\'%02x", (BYTE
)letter
[i
]);
777 if (pos
>= STREAMOUT_BUFFER_SIZE
- 11) {
778 if (!ME_StreamOutMove(pStream
, buffer
, pos
))
783 return ME_StreamOutMove(pStream
, buffer
, pos
);
787 static BOOL
ME_StreamOutRTF(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
788 const ME_Cursor
*start
, int nChars
, int dwFormat
)
790 ME_Cursor cursor
= *start
;
791 ME_DisplayItem
*prev_para
= cursor
.pPara
;
792 ME_Cursor endCur
= cursor
;
795 actual_chars
= ME_MoveCursorChars(editor
, &endCur
, nChars
);
796 /* Include the final \r which MoveCursorChars will ignore. */
797 if (actual_chars
!= nChars
) endCur
.nOffset
++;
799 if (!ME_StreamOutRTFHeader(pStream
, dwFormat
))
802 if (!ME_StreamOutRTFFontAndColorTbl(pStream
, cursor
.pRun
, endCur
.pRun
))
805 /* TODO: stylesheet table */
807 /* FIXME: maybe emit something smarter for the generator? */
808 if (!ME_StreamOutPrint(pStream
, "{\\*\\generator Wine Riched20 2.0.????;}"))
811 /* TODO: information group */
813 /* TODO: document formatting properties */
815 /* FIXME: We have only one document section */
817 /* TODO: section formatting properties */
819 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
823 if (cursor
.pPara
!= prev_para
)
825 prev_para
= cursor
.pPara
;
826 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
830 if (cursor
.pRun
== endCur
.pRun
&& !endCur
.nOffset
)
832 TRACE("flags %xh\n", cursor
.pRun
->member
.run
.nFlags
);
833 /* TODO: emit embedded objects */
834 if (cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
))
836 if (cursor
.pRun
->member
.run
.nFlags
& MERF_GRAPHICS
) {
837 FIXME("embedded objects are not handled\n");
838 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_TAB
) {
839 if (editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
840 cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
841 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
843 if (!ME_StreamOutPrint(pStream
, "\\cell "))
846 if (!ME_StreamOutPrint(pStream
, "\\tab "))
849 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDCELL
) {
850 if (pStream
->nNestingLevel
> 1) {
851 if (!ME_StreamOutPrint(pStream
, "\\nestcell "))
854 if (!ME_StreamOutPrint(pStream
, "\\cell "))
858 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
) {
859 if (cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
860 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
&&
861 !(cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)))
863 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
866 if (!ME_StreamOutPrint(pStream
, "\r\n\\par"))
869 /* Skip as many characters as required by current line break */
870 nChars
= max(0, nChars
- cursor
.pRun
->member
.run
.len
);
871 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDROW
) {
872 if (!ME_StreamOutPrint(pStream
, "\\line \r\n"))
878 if (!ME_StreamOutPrint(pStream
, "{"))
880 TRACE("style %p\n", cursor
.pRun
->member
.run
.style
);
881 if (!ME_StreamOutRTFCharProps(pStream
, &cursor
.pRun
->member
.run
.style
->fmt
))
884 nEnd
= (cursor
.pRun
== endCur
.pRun
) ? endCur
.nOffset
: cursor
.pRun
->member
.run
.len
;
885 if (!ME_StreamOutRTFText(pStream
, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
886 nEnd
- cursor
.nOffset
))
889 if (!ME_StreamOutPrint(pStream
, "}"))
892 } while (cursor
.pRun
!= endCur
.pRun
&& ME_NextRun(&cursor
.pPara
, &cursor
.pRun
));
894 if (!ME_StreamOutMove(pStream
, "}\0", 2))
900 static BOOL
ME_StreamOutText(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
901 const ME_Cursor
*start
, int nChars
, DWORD dwFormat
)
903 ME_Cursor cursor
= *start
;
905 UINT nCodePage
= CP_ACP
;
913 if (dwFormat
& SF_USECODEPAGE
)
914 nCodePage
= HIWORD(dwFormat
);
916 /* TODO: Handle SF_TEXTIZED */
918 while (success
&& nChars
&& cursor
.pRun
) {
919 nLen
= min(nChars
, cursor
.pRun
->member
.run
.len
- cursor
.nOffset
);
921 if (!editor
->bEmulateVersion10
&& cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
923 static const WCHAR szEOL
[] = { '\r', '\n' };
925 /* richedit 2.0 - all line breaks are \r\n */
926 if (dwFormat
& SF_UNICODE
)
927 success
= ME_StreamOutMove(pStream
, (const char *)szEOL
, sizeof(szEOL
));
929 success
= ME_StreamOutMove(pStream
, "\r\n", 2);
931 if (dwFormat
& SF_UNICODE
)
932 success
= ME_StreamOutMove(pStream
, (const char *)(get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
)),
933 sizeof(WCHAR
) * nLen
);
937 nSize
= WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
938 nLen
, NULL
, 0, NULL
, NULL
);
939 if (nSize
> nBufLen
) {
941 buffer
= ALLOC_N_OBJ(char, nSize
);
944 WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
945 nLen
, buffer
, nSize
, NULL
, NULL
);
946 success
= ME_StreamOutMove(pStream
, buffer
, nSize
);
952 cursor
.pRun
= ME_FindItemFwd(cursor
.pRun
, diRun
);
960 LRESULT
ME_StreamOutRange(ME_TextEditor
*editor
, DWORD dwFormat
,
961 const ME_Cursor
*start
,
962 int nChars
, EDITSTREAM
*stream
)
964 ME_OutStream
*pStream
= ME_StreamOutInit(editor
, stream
);
966 if (dwFormat
& SF_RTF
)
967 ME_StreamOutRTF(editor
, pStream
, start
, nChars
, dwFormat
);
968 else if (dwFormat
& SF_TEXT
|| dwFormat
& SF_TEXTIZED
)
969 ME_StreamOutText(editor
, pStream
, start
, nChars
, dwFormat
);
970 if (!pStream
->stream
->dwError
)
971 ME_StreamOutFlush(pStream
);
972 return ME_StreamOutFree(pStream
);
976 ME_StreamOut(ME_TextEditor
*editor
, DWORD dwFormat
, EDITSTREAM
*stream
)
981 if (dwFormat
& SFF_SELECTION
) {
983 start
= editor
->pCursors
[ME_GetSelectionOfs(editor
, &nStart
, &nTo
)];
984 nChars
= nTo
- nStart
;
986 ME_SetCursorToStart(editor
, &start
);
987 nChars
= ME_GetTextLength(editor
);
988 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
989 if (dwFormat
& SF_RTF
)
992 return ME_StreamOutRange(editor
, dwFormat
, &start
, nChars
, stream
);