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
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
28 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
);
32 ME_StreamOutInit(ME_TextEditor
*editor
, EDITSTREAM
*stream
)
34 ME_OutStream
*pStream
= ALLOC_OBJ(ME_OutStream
);
35 pStream
->stream
= stream
;
36 pStream
->stream
->dwError
= 0;
39 pStream
->nFontTblLen
= 0;
40 pStream
->nColorTblLen
= 1;
41 pStream
->nNestingLevel
= 0;
47 ME_StreamOutFlush(ME_OutStream
*pStream
)
52 EDITSTREAM
*stream
= pStream
->stream
;
54 while (nStart
< pStream
->pos
) {
55 TRACE("sending %u bytes\n", pStream
->pos
- nStart
);
56 /* Some apps seem not to set *pcb unless a problem arises, relying
57 on initial random nWritten value, which is usually >STREAMOUT_BUFFER_SIZE */
58 nRemaining
= pStream
->pos
- nStart
;
59 nWritten
= 0xDEADBEEF;
60 stream
->dwError
= stream
->pfnCallback(stream
->dwCookie
, (LPBYTE
)pStream
->buffer
+ nStart
,
61 pStream
->pos
- nStart
, &nWritten
);
62 TRACE("error=%u written=%u\n", stream
->dwError
, nWritten
);
63 if (nWritten
> (pStream
->pos
- nStart
) || nWritten
<0) {
64 FIXME("Invalid returned written size *pcb: 0x%x (%d) instead of %d\n",
65 (unsigned)nWritten
, nWritten
, nRemaining
);
66 nWritten
= nRemaining
;
68 if (nWritten
== 0 || stream
->dwError
)
70 pStream
->written
+= nWritten
;
79 ME_StreamOutFree(ME_OutStream
*pStream
)
81 LONG written
= pStream
->written
;
82 TRACE("total length = %u\n", written
);
90 ME_StreamOutMove(ME_OutStream
*pStream
, const char *buffer
, int len
)
93 int space
= STREAMOUT_BUFFER_SIZE
- pStream
->pos
;
94 int fit
= min(space
, len
);
96 TRACE("%u:%u:%s\n", pStream
->pos
, fit
, debugstr_an(buffer
,fit
));
97 memmove(pStream
->buffer
+ pStream
->pos
, buffer
, fit
);
101 if (pStream
->pos
== STREAMOUT_BUFFER_SIZE
) {
102 if (!ME_StreamOutFlush(pStream
))
111 ME_StreamOutPrint(ME_OutStream
*pStream
, const char *format
, ...)
113 char string
[STREAMOUT_BUFFER_SIZE
]; /* This is going to be enough */
117 va_start(valist
, format
);
118 len
= vsnprintf(string
, sizeof(string
), format
, valist
);
121 return ME_StreamOutMove(pStream
, string
, len
);
126 ME_StreamOutRTFHeader(ME_OutStream
*pStream
, int dwFormat
)
128 const char *cCharSet
= NULL
;
133 if (dwFormat
& SF_USECODEPAGE
) {
136 switch (HIWORD(dwFormat
)) {
139 nCodePage
= GetACP();
142 nCodePage
= GetOEMCP();
143 if (nCodePage
== 437)
145 else if (nCodePage
== 850)
154 if (HIWORD(dwFormat
) == CP_MACCP
) {
156 nCodePage
= 10000; /* MacRoman */
159 nCodePage
= 1252; /* Latin-1 */
161 if (GetCPInfoExW(HIWORD(dwFormat
), 0, &info
))
162 nCodePage
= info
.CodePage
;
166 /* TODO: If the original document contained an \ansicpg value, retain it.
167 * Otherwise, M$ richedit emits a codepage number determined from the
168 * charset of the default font here. Anyway, this value is not used by
170 nCodePage
= GetACP();
172 if (nCodePage
== CP_UTF8
)
173 success
= ME_StreamOutPrint(pStream
, "{\\urtf");
175 success
= ME_StreamOutPrint(pStream
, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet
, nCodePage
);
180 pStream
->nDefaultCodePage
= nCodePage
;
182 /* FIXME: This should be a document property */
183 /* TODO: handle SFF_PLAINRTF */
184 language
= GetUserDefaultLangID();
185 if (!ME_StreamOutPrint(pStream
, "\\deff0\\deflang%u\\deflangfe%u", language
, language
))
188 /* FIXME: This should be a document property */
189 pStream
->nDefaultFont
= 0;
196 ME_StreamOutRTFFontAndColorTbl(ME_OutStream
*pStream
, ME_DisplayItem
*pFirstRun
,
197 ME_DisplayItem
*pLastRun
)
199 ME_DisplayItem
*item
= pFirstRun
;
200 ME_FontTableItem
*table
= pStream
->fonttbl
;
202 ME_DisplayItem
*pLastPara
= ME_GetParagraph(pLastRun
);
203 ME_DisplayItem
*pCell
= NULL
;
206 CHARFORMAT2W
*fmt
= &item
->member
.run
.style
->fmt
;
209 if (fmt
->dwMask
& CFM_FACE
) {
210 WCHAR
*face
= fmt
->szFaceName
;
211 BYTE bCharSet
= (fmt
->dwMask
& CFM_CHARSET
) ? fmt
->bCharSet
: DEFAULT_CHARSET
;
213 for (i
= 0; i
< pStream
->nFontTblLen
; i
++)
214 if (table
[i
].bCharSet
== bCharSet
215 && (table
[i
].szFaceName
== face
|| !lstrcmpW(table
[i
].szFaceName
, face
)))
217 if (i
== pStream
->nFontTblLen
&& i
< STREAMOUT_FONTTBL_SIZE
) {
218 table
[i
].bCharSet
= bCharSet
;
219 table
[i
].szFaceName
= face
;
220 pStream
->nFontTblLen
++;
224 if (fmt
->dwMask
& CFM_COLOR
&& !(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
225 crColor
= fmt
->crTextColor
;
226 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
227 if (pStream
->colortbl
[i
] == crColor
)
229 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
230 pStream
->colortbl
[i
] = crColor
;
231 pStream
->nColorTblLen
++;
234 if (fmt
->dwMask
& CFM_BACKCOLOR
&& !(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
235 crColor
= fmt
->crBackColor
;
236 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
237 if (pStream
->colortbl
[i
] == crColor
)
239 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
240 pStream
->colortbl
[i
] = crColor
;
241 pStream
->nColorTblLen
++;
245 if (item
== pLastRun
)
247 item
= ME_FindItemFwd(item
, diRun
);
249 item
= ME_GetParagraph(pFirstRun
);
251 if ((pCell
= item
->member
.para
.pCell
))
253 ME_Border
* borders
[4] = { &pCell
->member
.cell
.border
.top
,
254 &pCell
->member
.cell
.border
.left
,
255 &pCell
->member
.cell
.border
.bottom
,
256 &pCell
->member
.cell
.border
.right
};
257 for (i
= 0; i
< 4; i
++)
259 if (borders
[i
]->width
> 0)
262 COLORREF crColor
= borders
[i
]->colorRef
;
263 for (j
= 1; j
< pStream
->nColorTblLen
; j
++)
264 if (pStream
->colortbl
[j
] == crColor
)
266 if (j
== pStream
->nColorTblLen
&& j
< STREAMOUT_COLORTBL_SIZE
) {
267 pStream
->colortbl
[j
] = crColor
;
268 pStream
->nColorTblLen
++;
273 if (item
== pLastPara
)
275 item
= item
->member
.para
.next_para
;
278 if (!ME_StreamOutPrint(pStream
, "{\\fonttbl"))
281 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
282 if (table
[i
].bCharSet
!= DEFAULT_CHARSET
) {
283 if (!ME_StreamOutPrint(pStream
, "{\\f%u\\fcharset%u ", i
, table
[i
].bCharSet
))
286 if (!ME_StreamOutPrint(pStream
, "{\\f%u ", i
))
289 if (!ME_StreamOutRTFText(pStream
, table
[i
].szFaceName
, -1))
291 if (!ME_StreamOutPrint(pStream
, ";}"))
294 if (!ME_StreamOutPrint(pStream
, "}\r\n"))
297 /* It seems like Open Office ignores \deff0 tag at RTF-header.
298 As result it can't correctly parse text before first \fN tag,
299 so we can put \f0 immediately after font table. This forces
300 parser to use the same font, that \deff0 specifies.
301 It makes OOffice happy */
302 if (!ME_StreamOutPrint(pStream
, "\\f0"))
305 /* Output the color table */
306 if (!ME_StreamOutPrint(pStream
, "{\\colortbl;")) return FALSE
; /* first entry is auto-color */
307 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
309 if (!ME_StreamOutPrint(pStream
, "\\red%u\\green%u\\blue%u;", pStream
->colortbl
[i
] & 0xFF,
310 (pStream
->colortbl
[i
] >> 8) & 0xFF, (pStream
->colortbl
[i
] >> 16) & 0xFF))
313 if (!ME_StreamOutPrint(pStream
, "}")) return FALSE
;
319 ME_StreamOutRTFTableProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
320 ME_DisplayItem
*para
)
322 ME_DisplayItem
*cell
;
323 char props
[STREAMOUT_BUFFER_SIZE
] = "";
325 const char sideChar
[4] = {'t','l','b','r'};
327 if (!ME_StreamOutPrint(pStream
, "\\trowd"))
329 if (!editor
->bEmulateVersion10
) { /* v4.1 */
330 PARAFORMAT2
*pFmt
= ME_GetTableRowEnd(para
)->member
.para
.pFmt
;
331 para
= ME_GetTableRowStart(para
);
332 cell
= para
->member
.para
.next_para
->member
.para
.pCell
;
335 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
336 if (pFmt
->dxStartIndent
)
337 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
339 ME_Border
* borders
[4] = { &cell
->member
.cell
.border
.top
,
340 &cell
->member
.cell
.border
.left
,
341 &cell
->member
.cell
.border
.bottom
,
342 &cell
->member
.cell
.border
.right
};
343 for (i
= 0; i
< 4; i
++)
345 if (borders
[i
]->width
)
348 COLORREF crColor
= borders
[i
]->colorRef
;
349 sprintf(props
+ strlen(props
), "\\clbrdr%c", sideChar
[i
]);
350 sprintf(props
+ strlen(props
), "\\brdrs");
351 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
352 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
353 if (pStream
->colortbl
[j
] == crColor
) {
354 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
360 sprintf(props
+ strlen(props
), "\\cellx%d", cell
->member
.cell
.nRightBoundary
);
361 cell
= cell
->member
.cell
.next_cell
;
362 } while (cell
->member
.cell
.next_cell
);
363 } else { /* v1.0 - 3.0 */
364 const ME_Border
* borders
[4] = { ¶
->member
.para
.border
.top
,
365 ¶
->member
.para
.border
.left
,
366 ¶
->member
.para
.border
.bottom
,
367 ¶
->member
.para
.border
.right
};
368 PARAFORMAT2
*pFmt
= para
->member
.para
.pFmt
;
370 assert(!(para
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)));
372 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
373 if (pFmt
->dxStartIndent
)
374 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
375 for (i
= 0; i
< 4; i
++)
377 if (borders
[i
]->width
)
380 COLORREF crColor
= borders
[i
]->colorRef
;
381 sprintf(props
+ strlen(props
), "\\trbrdr%c", sideChar
[i
]);
382 sprintf(props
+ strlen(props
), "\\brdrs");
383 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
384 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
385 if (pStream
->colortbl
[j
] == crColor
) {
386 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
392 for (i
= 0; i
< pFmt
->cTabCount
; i
++)
394 sprintf(props
+ strlen(props
), "\\cellx%d", pFmt
->rgxTabs
[i
] & 0x00FFFFFF);
397 if (!ME_StreamOutPrint(pStream
, props
))
404 ME_StreamOutRTFParaProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
405 ME_DisplayItem
*para
)
407 PARAFORMAT2
*fmt
= para
->member
.para
.pFmt
;
408 char props
[STREAMOUT_BUFFER_SIZE
] = "";
411 if (!editor
->bEmulateVersion10
) { /* v4.1 */
412 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
) {
413 pStream
->nNestingLevel
++;
414 if (pStream
->nNestingLevel
== 1) {
415 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
419 } else if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
420 pStream
->nNestingLevel
--;
421 if (pStream
->nNestingLevel
>= 1) {
422 if (!ME_StreamOutPrint(pStream
, "{\\*\\nesttableprops"))
424 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
426 if (!ME_StreamOutPrint(pStream
, "\\nestrow}{\\nonesttables\\par}\r\n"))
429 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
434 } else { /* v1.0 - 3.0 */
435 if (para
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
436 para
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
438 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
443 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
444 if (!ME_StreamOutPrint(pStream
, "\\pard"))
447 if (!editor
->bEmulateVersion10
) { /* v4.1 */
448 if (pStream
->nNestingLevel
> 0)
449 strcat(props
, "\\intbl");
450 if (pStream
->nNestingLevel
> 1)
451 sprintf(props
+ strlen(props
), "\\itap%d", pStream
->nNestingLevel
);
452 } else { /* v1.0 - 3.0 */
453 if (fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
)
454 strcat(props
, "\\intbl");
457 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
458 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
459 * set very different from the documentation.
460 * (Tested with RichEdit 5.50.25.0601) */
462 if (fmt
->dwMask
& PFM_ALIGNMENT
) {
463 switch (fmt
->wAlignment
) {
465 /* Default alignment: not emitted */
468 strcat(props
, "\\qr");
471 strcat(props
, "\\qc");
474 strcat(props
, "\\qj");
479 if (fmt
->dwMask
& PFM_LINESPACING
) {
480 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
481 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
482 switch (fmt
->bLineSpacingRule
) {
483 case 0: /* Single spacing */
484 strcat(props
, "\\sl-240\\slmult1");
486 case 1: /* 1.5 spacing */
487 strcat(props
, "\\sl-360\\slmult1");
489 case 2: /* Double spacing */
490 strcat(props
, "\\sl-480\\slmult1");
493 sprintf(props
+ strlen(props
), "\\sl%d\\slmult0", fmt
->dyLineSpacing
);
496 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult0", fmt
->dyLineSpacing
);
499 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult1", fmt
->dyLineSpacing
* 240 / 20);
504 if (fmt
->dwMask
& PFM_DONOTHYPHEN
&& fmt
->wEffects
& PFE_DONOTHYPHEN
)
505 strcat(props
, "\\hyph0");
506 if (fmt
->dwMask
& PFM_KEEP
&& fmt
->wEffects
& PFE_KEEP
)
507 strcat(props
, "\\keep");
508 if (fmt
->dwMask
& PFM_KEEPNEXT
&& fmt
->wEffects
& PFE_KEEPNEXT
)
509 strcat(props
, "\\keepn");
510 if (fmt
->dwMask
& PFM_NOLINENUMBER
&& fmt
->wEffects
& PFE_NOLINENUMBER
)
511 strcat(props
, "\\noline");
512 if (fmt
->dwMask
& PFM_NOWIDOWCONTROL
&& fmt
->wEffects
& PFE_NOWIDOWCONTROL
)
513 strcat(props
, "\\nowidctlpar");
514 if (fmt
->dwMask
& PFM_PAGEBREAKBEFORE
&& fmt
->wEffects
& PFE_PAGEBREAKBEFORE
)
515 strcat(props
, "\\pagebb");
516 if (fmt
->dwMask
& PFM_RTLPARA
&& fmt
->wEffects
& PFE_RTLPARA
)
517 strcat(props
, "\\rtlpar");
518 if (fmt
->dwMask
& PFM_SIDEBYSIDE
&& fmt
->wEffects
& PFE_SIDEBYSIDE
)
519 strcat(props
, "\\sbys");
521 if (!(editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
522 fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
))
524 if (fmt
->dwMask
& PFM_OFFSET
)
525 sprintf(props
+ strlen(props
), "\\li%d", fmt
->dxOffset
);
526 if (fmt
->dwMask
& PFM_OFFSETINDENT
|| fmt
->dwMask
& PFM_STARTINDENT
)
527 sprintf(props
+ strlen(props
), "\\fi%d", fmt
->dxStartIndent
);
528 if (fmt
->dwMask
& PFM_RIGHTINDENT
)
529 sprintf(props
+ strlen(props
), "\\ri%d", fmt
->dxRightIndent
);
530 if (fmt
->dwMask
& PFM_TABSTOPS
) {
531 static const char * const leader
[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
533 for (i
= 0; i
< fmt
->cTabCount
; i
++) {
534 switch ((fmt
->rgxTabs
[i
] >> 24) & 0xF) {
536 strcat(props
, "\\tqc");
539 strcat(props
, "\\tqr");
542 strcat(props
, "\\tqdec");
545 /* Word bar tab (vertical bar). Handled below */
548 if (fmt
->rgxTabs
[i
] >> 28 <= 5)
549 strcat(props
, leader
[fmt
->rgxTabs
[i
] >> 28]);
550 sprintf(props
+strlen(props
), "\\tx%d", fmt
->rgxTabs
[i
]&0x00FFFFFF);
554 if (fmt
->dwMask
& PFM_SPACEAFTER
)
555 sprintf(props
+ strlen(props
), "\\sa%d", fmt
->dySpaceAfter
);
556 if (fmt
->dwMask
& PFM_SPACEBEFORE
)
557 sprintf(props
+ strlen(props
), "\\sb%d", fmt
->dySpaceBefore
);
558 if (fmt
->dwMask
& PFM_STYLE
)
559 sprintf(props
+ strlen(props
), "\\s%d", fmt
->sStyle
);
561 if (fmt
->dwMask
& PFM_SHADING
) {
562 static const char * const style
[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
563 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
564 "\\bghoriz", "\\bgvert", "\\bgfdiag",
565 "\\bgbdiag", "\\bgcross", "\\bgdcross",
567 if (fmt
->wShadingWeight
)
568 sprintf(props
+ strlen(props
), "\\shading%d", fmt
->wShadingWeight
);
569 if (fmt
->wShadingStyle
& 0xF)
570 strcat(props
, style
[fmt
->wShadingStyle
& 0xF]);
571 sprintf(props
+ strlen(props
), "\\cfpat%d\\cbpat%d",
572 (fmt
->wShadingStyle
>> 4) & 0xF, (fmt
->wShadingStyle
>> 8) & 0xF);
575 if (*props
&& !ME_StreamOutPrint(pStream
, props
))
583 ME_StreamOutRTFCharProps(ME_OutStream
*pStream
, CHARFORMAT2W
*fmt
)
585 char props
[STREAMOUT_BUFFER_SIZE
] = "";
588 if (fmt
->dwMask
& CFM_ALLCAPS
&& fmt
->dwEffects
& CFE_ALLCAPS
)
589 strcat(props
, "\\caps");
590 if (fmt
->dwMask
& CFM_ANIMATION
)
591 sprintf(props
+ strlen(props
), "\\animtext%u", fmt
->bAnimation
);
592 if (fmt
->dwMask
& CFM_BACKCOLOR
) {
593 if (!(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
594 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
595 if (pStream
->colortbl
[i
] == fmt
->crBackColor
) {
596 sprintf(props
+ strlen(props
), "\\cb%u", i
);
601 if (fmt
->dwMask
& CFM_BOLD
&& fmt
->dwEffects
& CFE_BOLD
)
602 strcat(props
, "\\b");
603 if (fmt
->dwMask
& CFM_COLOR
) {
604 if (!(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
605 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
606 if (pStream
->colortbl
[i
] == fmt
->crTextColor
) {
607 sprintf(props
+ strlen(props
), "\\cf%u", i
);
612 /* TODO: CFM_DISABLED */
613 if (fmt
->dwMask
& CFM_EMBOSS
&& fmt
->dwEffects
& CFE_EMBOSS
)
614 strcat(props
, "\\embo");
615 if (fmt
->dwMask
& CFM_HIDDEN
&& fmt
->dwEffects
& CFE_HIDDEN
)
616 strcat(props
, "\\v");
617 if (fmt
->dwMask
& CFM_IMPRINT
&& fmt
->dwEffects
& CFE_IMPRINT
)
618 strcat(props
, "\\impr");
619 if (fmt
->dwMask
& CFM_ITALIC
&& fmt
->dwEffects
& CFE_ITALIC
)
620 strcat(props
, "\\i");
621 if (fmt
->dwMask
& CFM_KERNING
)
622 sprintf(props
+ strlen(props
), "\\kerning%u", fmt
->wKerning
);
623 if (fmt
->dwMask
& CFM_LCID
) {
624 /* TODO: handle SFF_PLAINRTF */
625 if (LOWORD(fmt
->lcid
) == 1024)
626 strcat(props
, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
628 sprintf(props
+ strlen(props
), "\\lang%u", LOWORD(fmt
->lcid
));
630 /* CFM_LINK is not streamed out by M$ */
631 if (fmt
->dwMask
& CFM_OFFSET
) {
632 if (fmt
->yOffset
>= 0)
633 sprintf(props
+ strlen(props
), "\\up%d", fmt
->yOffset
);
635 sprintf(props
+ strlen(props
), "\\dn%d", -fmt
->yOffset
);
637 if (fmt
->dwMask
& CFM_OUTLINE
&& fmt
->dwEffects
& CFE_OUTLINE
)
638 strcat(props
, "\\outl");
639 if (fmt
->dwMask
& CFM_PROTECTED
&& fmt
->dwEffects
& CFE_PROTECTED
)
640 strcat(props
, "\\protect");
641 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
642 if (fmt
->dwMask
& CFM_SHADOW
&& fmt
->dwEffects
& CFE_SHADOW
)
643 strcat(props
, "\\shad");
644 if (fmt
->dwMask
& CFM_SIZE
)
645 sprintf(props
+ strlen(props
), "\\fs%d", fmt
->yHeight
/ 10);
646 if (fmt
->dwMask
& CFM_SMALLCAPS
&& fmt
->dwEffects
& CFE_SMALLCAPS
)
647 strcat(props
, "\\scaps");
648 if (fmt
->dwMask
& CFM_SPACING
)
649 sprintf(props
+ strlen(props
), "\\expnd%u\\expndtw%u", fmt
->sSpacing
/ 5, fmt
->sSpacing
);
650 if (fmt
->dwMask
& CFM_STRIKEOUT
&& fmt
->dwEffects
& CFE_STRIKEOUT
)
651 strcat(props
, "\\strike");
652 if (fmt
->dwMask
& CFM_STYLE
) {
653 sprintf(props
+ strlen(props
), "\\cs%u", fmt
->sStyle
);
654 /* TODO: emit style contents here */
656 if (fmt
->dwMask
& (CFM_SUBSCRIPT
| CFM_SUPERSCRIPT
)) {
657 if (fmt
->dwEffects
& CFE_SUBSCRIPT
)
658 strcat(props
, "\\sub");
659 else if (fmt
->dwEffects
& CFE_SUPERSCRIPT
)
660 strcat(props
, "\\super");
662 if (fmt
->dwMask
& CFM_UNDERLINE
|| fmt
->dwMask
& CFM_UNDERLINETYPE
) {
663 if (fmt
->dwMask
& CFM_UNDERLINETYPE
)
664 switch (fmt
->bUnderlineType
) {
665 case CFU_CF1UNDERLINE
:
667 strcat(props
, "\\ul");
669 case CFU_UNDERLINEDOTTED
:
670 strcat(props
, "\\uld");
672 case CFU_UNDERLINEDOUBLE
:
673 strcat(props
, "\\uldb");
675 case CFU_UNDERLINEWORD
:
676 strcat(props
, "\\ulw");
678 case CFU_UNDERLINENONE
:
680 strcat(props
, "\\ulnone");
683 else if (fmt
->dwEffects
& CFE_UNDERLINE
)
684 strcat(props
, "\\ul");
686 /* FIXME: How to emit CFM_WEIGHT? */
688 if (fmt
->dwMask
& CFM_FACE
|| fmt
->dwMask
& CFM_CHARSET
) {
691 if (fmt
->dwMask
& CFM_FACE
)
692 szFaceName
= fmt
->szFaceName
;
694 szFaceName
= pStream
->fonttbl
[0].szFaceName
;
695 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
696 if (szFaceName
== pStream
->fonttbl
[i
].szFaceName
697 || !lstrcmpW(szFaceName
, pStream
->fonttbl
[i
].szFaceName
))
698 if (!(fmt
->dwMask
& CFM_CHARSET
)
699 || fmt
->bCharSet
== pStream
->fonttbl
[i
].bCharSet
)
702 if (i
< pStream
->nFontTblLen
)
704 if (i
!= pStream
->nDefaultFont
)
705 sprintf(props
+ strlen(props
), "\\f%u", i
);
707 /* In UTF-8 mode, charsets/codepages are not used */
708 if (pStream
->nDefaultCodePage
!= CP_UTF8
)
710 if (pStream
->fonttbl
[i
].bCharSet
== DEFAULT_CHARSET
)
711 pStream
->nCodePage
= pStream
->nDefaultCodePage
;
713 pStream
->nCodePage
= RTFCharSetToCodePage(NULL
, pStream
->fonttbl
[i
].bCharSet
);
719 if (!ME_StreamOutPrint(pStream
, props
))
726 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
)
728 char buffer
[STREAMOUT_BUFFER_SIZE
];
733 nChars
= lstrlenW(text
);
736 /* In UTF-8 mode, font charsets are not used. */
737 if (pStream
->nDefaultCodePage
== CP_UTF8
) {
738 /* 6 is the maximum character length in UTF-8 */
739 fit
= min(nChars
, STREAMOUT_BUFFER_SIZE
/ 6);
740 nBytes
= WideCharToMultiByte(CP_UTF8
, 0, text
, fit
, buffer
,
741 STREAMOUT_BUFFER_SIZE
, NULL
, NULL
);
744 for (i
= 0; i
< nBytes
; i
++)
745 if (buffer
[i
] == '{' || buffer
[i
] == '}' || buffer
[i
] == '\\') {
746 if (!ME_StreamOutPrint(pStream
, "%.*s\\", i
- pos
, buffer
+ pos
))
751 if (!ME_StreamOutMove(pStream
, buffer
+ pos
, nBytes
- pos
))
754 } else if (*text
< 128) {
755 if (*text
== '{' || *text
== '}' || *text
== '\\')
756 buffer
[pos
++] = '\\';
757 buffer
[pos
++] = (char)(*text
++);
760 BOOL unknown
= FALSE
;
763 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
764 * codepages including CP_SYMBOL for which the last parameter must be set
765 * to NULL for the function to succeed. But in Wine we need to care only
767 nBytes
= WideCharToMultiByte(pStream
->nCodePage
, 0, text
, 1,
769 (pStream
->nCodePage
== CP_SYMBOL
) ? NULL
: &unknown
);
771 pos
+= sprintf(buffer
+ pos
, "\\u%d?", (short)*text
);
772 else if ((BYTE
)*letter
< 128) {
773 if (*letter
== '{' || *letter
== '}' || *letter
== '\\')
774 buffer
[pos
++] = '\\';
775 buffer
[pos
++] = *letter
;
777 for (i
= 0; i
< nBytes
; i
++)
778 pos
+= sprintf(buffer
+ pos
, "\\'%02x", (BYTE
)letter
[i
]);
783 if (pos
>= STREAMOUT_BUFFER_SIZE
- 11) {
784 if (!ME_StreamOutMove(pStream
, buffer
, pos
))
789 return ME_StreamOutMove(pStream
, buffer
, pos
);
793 static BOOL
ME_StreamOutRTF(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
794 const ME_Cursor
*start
, int nChars
, int dwFormat
)
796 ME_Cursor cursor
= *start
;
797 ME_DisplayItem
*prev_para
= cursor
.pPara
;
798 ME_Cursor endCur
= cursor
;
800 ME_MoveCursorChars(editor
, &endCur
, nChars
);
802 if (!ME_StreamOutRTFHeader(pStream
, dwFormat
))
805 if (!ME_StreamOutRTFFontAndColorTbl(pStream
, cursor
.pRun
, endCur
.pRun
))
808 /* TODO: stylesheet table */
810 /* FIXME: maybe emit something smarter for the generator? */
811 if (!ME_StreamOutPrint(pStream
, "{\\*\\generator Wine Riched20 2.0.????;}"))
814 /* TODO: information group */
816 /* TODO: document formatting properties */
818 /* FIXME: We have only one document section */
820 /* TODO: section formatting properties */
822 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
826 if (cursor
.pPara
!= prev_para
)
828 prev_para
= cursor
.pPara
;
829 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
833 if (cursor
.pRun
== endCur
.pRun
&& !endCur
.nOffset
)
835 TRACE("flags %xh\n", cursor
.pRun
->member
.run
.nFlags
);
836 /* TODO: emit embedded objects */
837 if (cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
))
839 if (cursor
.pRun
->member
.run
.nFlags
& MERF_GRAPHICS
) {
840 FIXME("embedded objects are not handled\n");
841 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_TAB
) {
842 if (editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
843 cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
844 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
846 if (!ME_StreamOutPrint(pStream
, "\\cell "))
849 if (!ME_StreamOutPrint(pStream
, "\\tab "))
852 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDCELL
) {
853 if (pStream
->nNestingLevel
> 1) {
854 if (!ME_StreamOutPrint(pStream
, "\\nestcell "))
857 if (!ME_StreamOutPrint(pStream
, "\\cell "))
861 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
) {
862 if (cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
863 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
&&
864 !(cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)))
866 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
869 if (!ME_StreamOutPrint(pStream
, "\r\n\\par"))
872 /* Skip as many characters as required by current line break */
873 nChars
= max(0, nChars
- cursor
.pRun
->member
.run
.len
);
874 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDROW
) {
875 if (!ME_StreamOutPrint(pStream
, "\\line \r\n"))
881 if (!ME_StreamOutPrint(pStream
, "{"))
883 TRACE("style %p\n", cursor
.pRun
->member
.run
.style
);
884 if (!ME_StreamOutRTFCharProps(pStream
, &cursor
.pRun
->member
.run
.style
->fmt
))
887 nEnd
= (cursor
.pRun
== endCur
.pRun
) ? endCur
.nOffset
: cursor
.pRun
->member
.run
.len
;
888 if (!ME_StreamOutRTFText(pStream
, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
889 nEnd
- cursor
.nOffset
))
892 if (!ME_StreamOutPrint(pStream
, "}"))
895 } while (cursor
.pRun
!= endCur
.pRun
&& ME_NextRun(&cursor
.pPara
, &cursor
.pRun
));
897 if (!ME_StreamOutMove(pStream
, "}\0", 2))
903 static BOOL
ME_StreamOutText(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
904 const ME_Cursor
*start
, int nChars
, DWORD dwFormat
)
906 ME_Cursor cursor
= *start
;
908 UINT nCodePage
= CP_ACP
;
916 if (dwFormat
& SF_USECODEPAGE
)
917 nCodePage
= HIWORD(dwFormat
);
919 /* TODO: Handle SF_TEXTIZED */
921 while (success
&& nChars
&& cursor
.pRun
) {
922 nLen
= min(nChars
, cursor
.pRun
->member
.run
.len
- cursor
.nOffset
);
924 if (!editor
->bEmulateVersion10
&& cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
926 static const WCHAR szEOL
[] = { '\r', '\n' };
928 /* richedit 2.0 - all line breaks are \r\n */
929 if (dwFormat
& SF_UNICODE
)
930 success
= ME_StreamOutMove(pStream
, (const char *)szEOL
, sizeof(szEOL
));
932 success
= ME_StreamOutMove(pStream
, "\r\n", 2);
934 if (dwFormat
& SF_UNICODE
)
935 success
= ME_StreamOutMove(pStream
, (const char *)(get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
)),
936 sizeof(WCHAR
) * nLen
);
940 nSize
= WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
941 nLen
, NULL
, 0, NULL
, NULL
);
942 if (nSize
> nBufLen
) {
944 buffer
= ALLOC_N_OBJ(char, nSize
);
947 WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
948 nLen
, buffer
, nSize
, NULL
, NULL
);
949 success
= ME_StreamOutMove(pStream
, buffer
, nSize
);
955 cursor
.pRun
= ME_FindItemFwd(cursor
.pRun
, diRun
);
963 LRESULT
ME_StreamOutRange(ME_TextEditor
*editor
, DWORD dwFormat
,
964 const ME_Cursor
*start
,
965 int nChars
, EDITSTREAM
*stream
)
967 ME_OutStream
*pStream
= ME_StreamOutInit(editor
, stream
);
969 if (dwFormat
& SF_RTF
)
970 ME_StreamOutRTF(editor
, pStream
, start
, nChars
, dwFormat
);
971 else if (dwFormat
& SF_TEXT
|| dwFormat
& SF_TEXTIZED
)
972 ME_StreamOutText(editor
, pStream
, start
, nChars
, dwFormat
);
973 if (!pStream
->stream
->dwError
)
974 ME_StreamOutFlush(pStream
);
975 return ME_StreamOutFree(pStream
);
979 ME_StreamOut(ME_TextEditor
*editor
, DWORD dwFormat
, EDITSTREAM
*stream
)
984 if (dwFormat
& SFF_SELECTION
) {
986 start
= editor
->pCursors
[ME_GetSelectionOfs(editor
, &nStart
, &nTo
)];
987 nChars
= nTo
- nStart
;
989 ME_SetCursorToStart(editor
, &start
);
990 nChars
= ME_GetTextLength(editor
);
991 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
992 if (dwFormat
& SF_RTF
)
995 return ME_StreamOutRange(editor
, dwFormat
, &start
, nChars
, stream
);