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