bedfd3fcb64291ca2c88d06f8a3e47cf4f00a9e0
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
)
50 EDITSTREAM
*stream
= pStream
->stream
;
53 TRACE("sending %u bytes\n", pStream
->pos
);
54 nWritten
= pStream
->pos
;
55 stream
->dwError
= stream
->pfnCallback(stream
->dwCookie
, (LPBYTE
)pStream
->buffer
,
56 pStream
->pos
, &nWritten
);
57 TRACE("error=%u written=%u\n", stream
->dwError
, nWritten
);
58 if (nWritten
== 0 || stream
->dwError
)
60 /* Don't resend partial chunks if nWritten < pStream->pos */
68 ME_StreamOutFree(ME_OutStream
*pStream
)
70 LONG written
= pStream
->written
;
71 TRACE("total length = %u\n", written
);
79 ME_StreamOutMove(ME_OutStream
*pStream
, const char *buffer
, int len
)
82 int space
= STREAMOUT_BUFFER_SIZE
- pStream
->pos
;
83 int fit
= min(space
, len
);
85 TRACE("%u:%u:%s\n", pStream
->pos
, fit
, debugstr_an(buffer
,fit
));
86 memmove(pStream
->buffer
+ pStream
->pos
, buffer
, fit
);
90 if (pStream
->pos
== STREAMOUT_BUFFER_SIZE
) {
91 if (!ME_StreamOutFlush(pStream
))
100 ME_StreamOutPrint(ME_OutStream
*pStream
, const char *format
, ...)
102 char string
[STREAMOUT_BUFFER_SIZE
]; /* This is going to be enough */
106 va_start(valist
, format
);
107 len
= vsnprintf(string
, sizeof(string
), format
, valist
);
110 return ME_StreamOutMove(pStream
, string
, len
);
115 ME_StreamOutRTFHeader(ME_OutStream
*pStream
, int dwFormat
)
117 const char *cCharSet
= NULL
;
122 if (dwFormat
& SF_USECODEPAGE
) {
125 switch (HIWORD(dwFormat
)) {
128 nCodePage
= GetACP();
131 nCodePage
= GetOEMCP();
132 if (nCodePage
== 437)
134 else if (nCodePage
== 850)
143 if (HIWORD(dwFormat
) == CP_MACCP
) {
145 nCodePage
= 10000; /* MacRoman */
148 nCodePage
= 1252; /* Latin-1 */
150 if (GetCPInfoExW(HIWORD(dwFormat
), 0, &info
))
151 nCodePage
= info
.CodePage
;
155 /* TODO: If the original document contained an \ansicpg value, retain it.
156 * Otherwise, M$ richedit emits a codepage number determined from the
157 * charset of the default font here. Anyway, this value is not used by
159 nCodePage
= GetACP();
161 if (nCodePage
== CP_UTF8
)
162 success
= ME_StreamOutPrint(pStream
, "{\\urtf");
164 success
= ME_StreamOutPrint(pStream
, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet
, nCodePage
);
169 pStream
->nDefaultCodePage
= nCodePage
;
171 /* FIXME: This should be a document property */
172 /* TODO: handle SFF_PLAINRTF */
173 language
= GetUserDefaultLangID();
174 if (!ME_StreamOutPrint(pStream
, "\\deff0\\deflang%u\\deflangfe%u", language
, language
))
177 /* FIXME: This should be a document property */
178 pStream
->nDefaultFont
= 0;
185 ME_StreamOutRTFFontAndColorTbl(ME_OutStream
*pStream
, ME_DisplayItem
*pFirstRun
,
186 ME_DisplayItem
*pLastRun
)
188 ME_DisplayItem
*item
= pFirstRun
;
189 ME_FontTableItem
*table
= pStream
->fonttbl
;
191 ME_DisplayItem
*pLastPara
= ME_GetParagraph(pLastRun
);
192 ME_DisplayItem
*pCell
= NULL
;
195 CHARFORMAT2W
*fmt
= &item
->member
.run
.style
->fmt
;
198 if (fmt
->dwMask
& CFM_FACE
) {
199 WCHAR
*face
= fmt
->szFaceName
;
200 BYTE bCharSet
= (fmt
->dwMask
& CFM_CHARSET
) ? fmt
->bCharSet
: DEFAULT_CHARSET
;
202 for (i
= 0; i
< pStream
->nFontTblLen
; i
++)
203 if (table
[i
].bCharSet
== bCharSet
204 && (table
[i
].szFaceName
== face
|| !lstrcmpW(table
[i
].szFaceName
, face
)))
206 if (i
== pStream
->nFontTblLen
&& i
< STREAMOUT_FONTTBL_SIZE
) {
207 table
[i
].bCharSet
= bCharSet
;
208 table
[i
].szFaceName
= face
;
209 pStream
->nFontTblLen
++;
213 if (fmt
->dwMask
& CFM_COLOR
&& !(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
214 crColor
= fmt
->crTextColor
;
215 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
216 if (pStream
->colortbl
[i
] == crColor
)
218 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
219 pStream
->colortbl
[i
] = crColor
;
220 pStream
->nColorTblLen
++;
223 if (fmt
->dwMask
& CFM_BACKCOLOR
&& !(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
224 crColor
= fmt
->crBackColor
;
225 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
226 if (pStream
->colortbl
[i
] == crColor
)
228 if (i
== pStream
->nColorTblLen
&& i
< STREAMOUT_COLORTBL_SIZE
) {
229 pStream
->colortbl
[i
] = crColor
;
230 pStream
->nColorTblLen
++;
234 if (item
== pLastRun
)
236 item
= ME_FindItemFwd(item
, diRun
);
238 item
= ME_GetParagraph(pFirstRun
);
240 if ((pCell
= item
->member
.para
.pCell
))
242 ME_Border
* borders
[4] = { &pCell
->member
.cell
.border
.top
,
243 &pCell
->member
.cell
.border
.left
,
244 &pCell
->member
.cell
.border
.bottom
,
245 &pCell
->member
.cell
.border
.right
};
246 for (i
= 0; i
< 4; i
++)
248 if (borders
[i
]->width
> 0)
251 COLORREF crColor
= borders
[i
]->colorRef
;
252 for (j
= 1; j
< pStream
->nColorTblLen
; j
++)
253 if (pStream
->colortbl
[j
] == crColor
)
255 if (j
== pStream
->nColorTblLen
&& j
< STREAMOUT_COLORTBL_SIZE
) {
256 pStream
->colortbl
[j
] = crColor
;
257 pStream
->nColorTblLen
++;
262 if (item
== pLastPara
)
264 item
= item
->member
.para
.next_para
;
267 if (!ME_StreamOutPrint(pStream
, "{\\fonttbl"))
270 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
271 if (table
[i
].bCharSet
!= DEFAULT_CHARSET
) {
272 if (!ME_StreamOutPrint(pStream
, "{\\f%u\\fcharset%u ", i
, table
[i
].bCharSet
))
275 if (!ME_StreamOutPrint(pStream
, "{\\f%u ", i
))
278 if (!ME_StreamOutRTFText(pStream
, table
[i
].szFaceName
, -1))
280 if (!ME_StreamOutPrint(pStream
, ";}"))
283 if (!ME_StreamOutPrint(pStream
, "}\r\n"))
286 /* It seems like Open Office ignores \deff0 tag at RTF-header.
287 As result it can't correctly parse text before first \fN tag,
288 so we can put \f0 immediately after font table. This forces
289 parser to use the same font, that \deff0 specifies.
290 It makes OOffice happy */
291 if (!ME_StreamOutPrint(pStream
, "\\f0"))
294 /* Output the color table */
295 if (!ME_StreamOutPrint(pStream
, "{\\colortbl;")) return FALSE
; /* first entry is auto-color */
296 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
298 if (!ME_StreamOutPrint(pStream
, "\\red%u\\green%u\\blue%u;", pStream
->colortbl
[i
] & 0xFF,
299 (pStream
->colortbl
[i
] >> 8) & 0xFF, (pStream
->colortbl
[i
] >> 16) & 0xFF))
302 if (!ME_StreamOutPrint(pStream
, "}")) return FALSE
;
308 ME_StreamOutRTFTableProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
309 ME_DisplayItem
*para
)
311 ME_DisplayItem
*cell
;
312 char props
[STREAMOUT_BUFFER_SIZE
] = "";
314 const char sideChar
[4] = {'t','l','b','r'};
316 if (!ME_StreamOutPrint(pStream
, "\\trowd"))
318 if (!editor
->bEmulateVersion10
) { /* v4.1 */
319 PARAFORMAT2
*pFmt
= ME_GetTableRowEnd(para
)->member
.para
.pFmt
;
320 para
= ME_GetTableRowStart(para
);
321 cell
= para
->member
.para
.next_para
->member
.para
.pCell
;
324 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
325 if (pFmt
->dxStartIndent
)
326 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
328 ME_Border
* borders
[4] = { &cell
->member
.cell
.border
.top
,
329 &cell
->member
.cell
.border
.left
,
330 &cell
->member
.cell
.border
.bottom
,
331 &cell
->member
.cell
.border
.right
};
332 for (i
= 0; i
< 4; i
++)
334 if (borders
[i
]->width
)
337 COLORREF crColor
= borders
[i
]->colorRef
;
338 sprintf(props
+ strlen(props
), "\\clbrdr%c", sideChar
[i
]);
339 sprintf(props
+ strlen(props
), "\\brdrs");
340 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
341 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
342 if (pStream
->colortbl
[j
] == crColor
) {
343 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
349 sprintf(props
+ strlen(props
), "\\cellx%d", cell
->member
.cell
.nRightBoundary
);
350 cell
= cell
->member
.cell
.next_cell
;
351 } while (cell
->member
.cell
.next_cell
);
352 } else { /* v1.0 - 3.0 */
353 const ME_Border
* borders
[4] = { ¶
->member
.para
.border
.top
,
354 ¶
->member
.para
.border
.left
,
355 ¶
->member
.para
.border
.bottom
,
356 ¶
->member
.para
.border
.right
};
357 PARAFORMAT2
*pFmt
= para
->member
.para
.pFmt
;
359 assert(!(para
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)));
361 sprintf(props
+ strlen(props
), "\\trgaph%d", pFmt
->dxOffset
);
362 if (pFmt
->dxStartIndent
)
363 sprintf(props
+ strlen(props
), "\\trleft%d", pFmt
->dxStartIndent
);
364 for (i
= 0; i
< 4; i
++)
366 if (borders
[i
]->width
)
369 COLORREF crColor
= borders
[i
]->colorRef
;
370 sprintf(props
+ strlen(props
), "\\trbrdr%c", sideChar
[i
]);
371 sprintf(props
+ strlen(props
), "\\brdrs");
372 sprintf(props
+ strlen(props
), "\\brdrw%d", borders
[i
]->width
);
373 for (j
= 1; j
< pStream
->nColorTblLen
; j
++) {
374 if (pStream
->colortbl
[j
] == crColor
) {
375 sprintf(props
+ strlen(props
), "\\brdrcf%u", j
);
381 for (i
= 0; i
< pFmt
->cTabCount
; i
++)
383 sprintf(props
+ strlen(props
), "\\cellx%d", pFmt
->rgxTabs
[i
] & 0x00FFFFFF);
386 if (!ME_StreamOutPrint(pStream
, props
))
393 ME_StreamOutRTFParaProps(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
394 ME_DisplayItem
*para
)
396 PARAFORMAT2
*fmt
= para
->member
.para
.pFmt
;
397 char props
[STREAMOUT_BUFFER_SIZE
] = "";
400 if (!editor
->bEmulateVersion10
) { /* v4.1 */
401 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
) {
402 pStream
->nNestingLevel
++;
403 if (pStream
->nNestingLevel
== 1) {
404 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
408 } else if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
409 pStream
->nNestingLevel
--;
410 if (pStream
->nNestingLevel
>= 1) {
411 if (!ME_StreamOutPrint(pStream
, "{\\*\\nesttableprops"))
413 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
415 if (!ME_StreamOutPrint(pStream
, "\\nestrow}{\\nonesttables\\par}\r\n"))
418 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
423 } else { /* v1.0 - 3.0 */
424 if (para
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
425 para
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
427 if (!ME_StreamOutRTFTableProps(editor
, pStream
, para
))
432 /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */
433 if (!ME_StreamOutPrint(pStream
, "\\pard"))
436 if (!editor
->bEmulateVersion10
) { /* v4.1 */
437 if (pStream
->nNestingLevel
> 0)
438 strcat(props
, "\\intbl");
439 if (pStream
->nNestingLevel
> 1)
440 sprintf(props
+ strlen(props
), "\\itap%d", pStream
->nNestingLevel
);
441 } else { /* v1.0 - 3.0 */
442 if (fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
)
443 strcat(props
, "\\intbl");
446 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
447 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
448 * set very different from the documentation.
449 * (Tested with RichEdit 5.50.25.0601) */
451 if (fmt
->dwMask
& PFM_ALIGNMENT
) {
452 switch (fmt
->wAlignment
) {
454 /* Default alignment: not emitted */
457 strcat(props
, "\\qr");
460 strcat(props
, "\\qc");
463 strcat(props
, "\\qj");
468 if (fmt
->dwMask
& PFM_LINESPACING
) {
469 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
470 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
471 switch (fmt
->bLineSpacingRule
) {
472 case 0: /* Single spacing */
473 strcat(props
, "\\sl-240\\slmult1");
475 case 1: /* 1.5 spacing */
476 strcat(props
, "\\sl-360\\slmult1");
478 case 2: /* Double spacing */
479 strcat(props
, "\\sl-480\\slmult1");
482 sprintf(props
+ strlen(props
), "\\sl%d\\slmult0", fmt
->dyLineSpacing
);
485 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult0", fmt
->dyLineSpacing
);
488 sprintf(props
+ strlen(props
), "\\sl-%d\\slmult1", fmt
->dyLineSpacing
* 240 / 20);
493 if (fmt
->dwMask
& PFM_DONOTHYPHEN
&& fmt
->wEffects
& PFE_DONOTHYPHEN
)
494 strcat(props
, "\\hyph0");
495 if (fmt
->dwMask
& PFM_KEEP
&& fmt
->wEffects
& PFE_KEEP
)
496 strcat(props
, "\\keep");
497 if (fmt
->dwMask
& PFM_KEEPNEXT
&& fmt
->wEffects
& PFE_KEEPNEXT
)
498 strcat(props
, "\\keepn");
499 if (fmt
->dwMask
& PFM_NOLINENUMBER
&& fmt
->wEffects
& PFE_NOLINENUMBER
)
500 strcat(props
, "\\noline");
501 if (fmt
->dwMask
& PFM_NOWIDOWCONTROL
&& fmt
->wEffects
& PFE_NOWIDOWCONTROL
)
502 strcat(props
, "\\nowidctlpar");
503 if (fmt
->dwMask
& PFM_PAGEBREAKBEFORE
&& fmt
->wEffects
& PFE_PAGEBREAKBEFORE
)
504 strcat(props
, "\\pagebb");
505 if (fmt
->dwMask
& PFM_RTLPARA
&& fmt
->wEffects
& PFE_RTLPARA
)
506 strcat(props
, "\\rtlpar");
507 if (fmt
->dwMask
& PFM_SIDEBYSIDE
&& fmt
->wEffects
& PFE_SIDEBYSIDE
)
508 strcat(props
, "\\sbys");
510 if (!(editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
511 fmt
->dwMask
& PFM_TABLE
&& fmt
->wEffects
& PFE_TABLE
))
513 if (fmt
->dwMask
& PFM_OFFSET
)
514 sprintf(props
+ strlen(props
), "\\li%d", fmt
->dxOffset
);
515 if (fmt
->dwMask
& PFM_OFFSETINDENT
|| fmt
->dwMask
& PFM_STARTINDENT
)
516 sprintf(props
+ strlen(props
), "\\fi%d", fmt
->dxStartIndent
);
517 if (fmt
->dwMask
& PFM_RIGHTINDENT
)
518 sprintf(props
+ strlen(props
), "\\ri%d", fmt
->dxRightIndent
);
519 if (fmt
->dwMask
& PFM_TABSTOPS
) {
520 static const char * const leader
[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
522 for (i
= 0; i
< fmt
->cTabCount
; i
++) {
523 switch ((fmt
->rgxTabs
[i
] >> 24) & 0xF) {
525 strcat(props
, "\\tqc");
528 strcat(props
, "\\tqr");
531 strcat(props
, "\\tqdec");
534 /* Word bar tab (vertical bar). Handled below */
537 if (fmt
->rgxTabs
[i
] >> 28 <= 5)
538 strcat(props
, leader
[fmt
->rgxTabs
[i
] >> 28]);
539 sprintf(props
+strlen(props
), "\\tx%d", fmt
->rgxTabs
[i
]&0x00FFFFFF);
543 if (fmt
->dwMask
& PFM_SPACEAFTER
)
544 sprintf(props
+ strlen(props
), "\\sa%d", fmt
->dySpaceAfter
);
545 if (fmt
->dwMask
& PFM_SPACEBEFORE
)
546 sprintf(props
+ strlen(props
), "\\sb%d", fmt
->dySpaceBefore
);
547 if (fmt
->dwMask
& PFM_STYLE
)
548 sprintf(props
+ strlen(props
), "\\s%d", fmt
->sStyle
);
550 if (fmt
->dwMask
& PFM_SHADING
) {
551 static const char * const style
[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
552 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
553 "\\bghoriz", "\\bgvert", "\\bgfdiag",
554 "\\bgbdiag", "\\bgcross", "\\bgdcross",
556 if (fmt
->wShadingWeight
)
557 sprintf(props
+ strlen(props
), "\\shading%d", fmt
->wShadingWeight
);
558 if (fmt
->wShadingStyle
& 0xF)
559 strcat(props
, style
[fmt
->wShadingStyle
& 0xF]);
560 sprintf(props
+ strlen(props
), "\\cfpat%d\\cbpat%d",
561 (fmt
->wShadingStyle
>> 4) & 0xF, (fmt
->wShadingStyle
>> 8) & 0xF);
564 if (*props
&& !ME_StreamOutPrint(pStream
, props
))
572 ME_StreamOutRTFCharProps(ME_OutStream
*pStream
, CHARFORMAT2W
*fmt
)
574 char props
[STREAMOUT_BUFFER_SIZE
] = "";
577 if (fmt
->dwMask
& CFM_ALLCAPS
&& fmt
->dwEffects
& CFE_ALLCAPS
)
578 strcat(props
, "\\caps");
579 if (fmt
->dwMask
& CFM_ANIMATION
)
580 sprintf(props
+ strlen(props
), "\\animtext%u", fmt
->bAnimation
);
581 if (fmt
->dwMask
& CFM_BACKCOLOR
) {
582 if (!(fmt
->dwEffects
& CFE_AUTOBACKCOLOR
)) {
583 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
584 if (pStream
->colortbl
[i
] == fmt
->crBackColor
) {
585 sprintf(props
+ strlen(props
), "\\cb%u", i
);
590 if (fmt
->dwMask
& CFM_BOLD
&& fmt
->dwEffects
& CFE_BOLD
)
591 strcat(props
, "\\b");
592 if (fmt
->dwMask
& CFM_COLOR
) {
593 if (!(fmt
->dwEffects
& CFE_AUTOCOLOR
)) {
594 for (i
= 1; i
< pStream
->nColorTblLen
; i
++)
595 if (pStream
->colortbl
[i
] == fmt
->crTextColor
) {
596 sprintf(props
+ strlen(props
), "\\cf%u", i
);
601 /* TODO: CFM_DISABLED */
602 if (fmt
->dwMask
& CFM_EMBOSS
&& fmt
->dwEffects
& CFE_EMBOSS
)
603 strcat(props
, "\\embo");
604 if (fmt
->dwMask
& CFM_HIDDEN
&& fmt
->dwEffects
& CFE_HIDDEN
)
605 strcat(props
, "\\v");
606 if (fmt
->dwMask
& CFM_IMPRINT
&& fmt
->dwEffects
& CFE_IMPRINT
)
607 strcat(props
, "\\impr");
608 if (fmt
->dwMask
& CFM_ITALIC
&& fmt
->dwEffects
& CFE_ITALIC
)
609 strcat(props
, "\\i");
610 if (fmt
->dwMask
& CFM_KERNING
)
611 sprintf(props
+ strlen(props
), "\\kerning%u", fmt
->wKerning
);
612 if (fmt
->dwMask
& CFM_LCID
) {
613 /* TODO: handle SFF_PLAINRTF */
614 if (LOWORD(fmt
->lcid
) == 1024)
615 strcat(props
, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
617 sprintf(props
+ strlen(props
), "\\lang%u", LOWORD(fmt
->lcid
));
619 /* CFM_LINK is not streamed out by M$ */
620 if (fmt
->dwMask
& CFM_OFFSET
) {
621 if (fmt
->yOffset
>= 0)
622 sprintf(props
+ strlen(props
), "\\up%d", fmt
->yOffset
);
624 sprintf(props
+ strlen(props
), "\\dn%d", -fmt
->yOffset
);
626 if (fmt
->dwMask
& CFM_OUTLINE
&& fmt
->dwEffects
& CFE_OUTLINE
)
627 strcat(props
, "\\outl");
628 if (fmt
->dwMask
& CFM_PROTECTED
&& fmt
->dwEffects
& CFE_PROTECTED
)
629 strcat(props
, "\\protect");
630 /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */
631 if (fmt
->dwMask
& CFM_SHADOW
&& fmt
->dwEffects
& CFE_SHADOW
)
632 strcat(props
, "\\shad");
633 if (fmt
->dwMask
& CFM_SIZE
)
634 sprintf(props
+ strlen(props
), "\\fs%d", fmt
->yHeight
/ 10);
635 if (fmt
->dwMask
& CFM_SMALLCAPS
&& fmt
->dwEffects
& CFE_SMALLCAPS
)
636 strcat(props
, "\\scaps");
637 if (fmt
->dwMask
& CFM_SPACING
)
638 sprintf(props
+ strlen(props
), "\\expnd%u\\expndtw%u", fmt
->sSpacing
/ 5, fmt
->sSpacing
);
639 if (fmt
->dwMask
& CFM_STRIKEOUT
&& fmt
->dwEffects
& CFE_STRIKEOUT
)
640 strcat(props
, "\\strike");
641 if (fmt
->dwMask
& CFM_STYLE
) {
642 sprintf(props
+ strlen(props
), "\\cs%u", fmt
->sStyle
);
643 /* TODO: emit style contents here */
645 if (fmt
->dwMask
& (CFM_SUBSCRIPT
| CFM_SUPERSCRIPT
)) {
646 if (fmt
->dwEffects
& CFE_SUBSCRIPT
)
647 strcat(props
, "\\sub");
648 else if (fmt
->dwEffects
& CFE_SUPERSCRIPT
)
649 strcat(props
, "\\super");
651 if (fmt
->dwMask
& CFM_UNDERLINE
|| fmt
->dwMask
& CFM_UNDERLINETYPE
) {
652 if (fmt
->dwMask
& CFM_UNDERLINETYPE
)
653 switch (fmt
->bUnderlineType
) {
654 case CFU_CF1UNDERLINE
:
656 strcat(props
, "\\ul");
658 case CFU_UNDERLINEDOTTED
:
659 strcat(props
, "\\uld");
661 case CFU_UNDERLINEDOUBLE
:
662 strcat(props
, "\\uldb");
664 case CFU_UNDERLINEWORD
:
665 strcat(props
, "\\ulw");
667 case CFU_UNDERLINENONE
:
669 strcat(props
, "\\ulnone");
672 else if (fmt
->dwEffects
& CFE_UNDERLINE
)
673 strcat(props
, "\\ul");
675 /* FIXME: How to emit CFM_WEIGHT? */
677 if (fmt
->dwMask
& CFM_FACE
|| fmt
->dwMask
& CFM_CHARSET
) {
680 if (fmt
->dwMask
& CFM_FACE
)
681 szFaceName
= fmt
->szFaceName
;
683 szFaceName
= pStream
->fonttbl
[0].szFaceName
;
684 for (i
= 0; i
< pStream
->nFontTblLen
; i
++) {
685 if (szFaceName
== pStream
->fonttbl
[i
].szFaceName
686 || !lstrcmpW(szFaceName
, pStream
->fonttbl
[i
].szFaceName
))
687 if (!(fmt
->dwMask
& CFM_CHARSET
)
688 || fmt
->bCharSet
== pStream
->fonttbl
[i
].bCharSet
)
691 if (i
< pStream
->nFontTblLen
)
693 if (i
!= pStream
->nDefaultFont
)
694 sprintf(props
+ strlen(props
), "\\f%u", i
);
696 /* In UTF-8 mode, charsets/codepages are not used */
697 if (pStream
->nDefaultCodePage
!= CP_UTF8
)
699 if (pStream
->fonttbl
[i
].bCharSet
== DEFAULT_CHARSET
)
700 pStream
->nCodePage
= pStream
->nDefaultCodePage
;
702 pStream
->nCodePage
= RTFCharSetToCodePage(NULL
, pStream
->fonttbl
[i
].bCharSet
);
708 if (!ME_StreamOutPrint(pStream
, props
))
715 ME_StreamOutRTFText(ME_OutStream
*pStream
, const WCHAR
*text
, LONG nChars
)
717 char buffer
[STREAMOUT_BUFFER_SIZE
];
722 nChars
= lstrlenW(text
);
725 /* In UTF-8 mode, font charsets are not used. */
726 if (pStream
->nDefaultCodePage
== CP_UTF8
) {
727 /* 6 is the maximum character length in UTF-8 */
728 fit
= min(nChars
, STREAMOUT_BUFFER_SIZE
/ 6);
729 nBytes
= WideCharToMultiByte(CP_UTF8
, 0, text
, fit
, buffer
,
730 STREAMOUT_BUFFER_SIZE
, NULL
, NULL
);
733 for (i
= 0; i
< nBytes
; i
++)
734 if (buffer
[i
] == '{' || buffer
[i
] == '}' || buffer
[i
] == '\\') {
735 if (!ME_StreamOutPrint(pStream
, "%.*s\\", i
- pos
, buffer
+ pos
))
740 if (!ME_StreamOutMove(pStream
, buffer
+ pos
, nBytes
- pos
))
743 } else if (*text
< 128) {
744 if (*text
== '{' || *text
== '}' || *text
== '\\')
745 buffer
[pos
++] = '\\';
746 buffer
[pos
++] = (char)(*text
++);
749 BOOL unknown
= FALSE
;
752 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
753 * codepages including CP_SYMBOL for which the last parameter must be set
754 * to NULL for the function to succeed. But in Wine we need to care only
756 nBytes
= WideCharToMultiByte(pStream
->nCodePage
, 0, text
, 1,
758 (pStream
->nCodePage
== CP_SYMBOL
) ? NULL
: &unknown
);
760 pos
+= sprintf(buffer
+ pos
, "\\u%d?", (short)*text
);
761 else if ((BYTE
)*letter
< 128) {
762 if (*letter
== '{' || *letter
== '}' || *letter
== '\\')
763 buffer
[pos
++] = '\\';
764 buffer
[pos
++] = *letter
;
766 for (i
= 0; i
< nBytes
; i
++)
767 pos
+= sprintf(buffer
+ pos
, "\\'%02x", (BYTE
)letter
[i
]);
772 if (pos
>= STREAMOUT_BUFFER_SIZE
- 11) {
773 if (!ME_StreamOutMove(pStream
, buffer
, pos
))
778 return ME_StreamOutMove(pStream
, buffer
, pos
);
782 static BOOL
ME_StreamOutRTF(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
783 const ME_Cursor
*start
, int nChars
, int dwFormat
)
785 ME_Cursor cursor
= *start
;
786 ME_DisplayItem
*prev_para
= cursor
.pPara
;
787 ME_Cursor endCur
= cursor
;
790 actual_chars
= ME_MoveCursorChars(editor
, &endCur
, nChars
);
791 /* Include the final \r which MoveCursorChars will ignore. */
792 if (actual_chars
!= nChars
) endCur
.nOffset
++;
794 if (!ME_StreamOutRTFHeader(pStream
, dwFormat
))
797 if (!ME_StreamOutRTFFontAndColorTbl(pStream
, cursor
.pRun
, endCur
.pRun
))
800 /* TODO: stylesheet table */
802 /* FIXME: maybe emit something smarter for the generator? */
803 if (!ME_StreamOutPrint(pStream
, "{\\*\\generator Wine Riched20 2.0.????;}"))
806 /* TODO: information group */
808 /* TODO: document formatting properties */
810 /* FIXME: We have only one document section */
812 /* TODO: section formatting properties */
814 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
818 if (cursor
.pPara
!= prev_para
)
820 prev_para
= cursor
.pPara
;
821 if (!ME_StreamOutRTFParaProps(editor
, pStream
, cursor
.pPara
))
825 if (cursor
.pRun
== endCur
.pRun
&& !endCur
.nOffset
)
827 TRACE("flags %xh\n", cursor
.pRun
->member
.run
.nFlags
);
828 /* TODO: emit embedded objects */
829 if (cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
))
831 if (cursor
.pRun
->member
.run
.nFlags
& MERF_GRAPHICS
) {
832 FIXME("embedded objects are not handled\n");
833 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_TAB
) {
834 if (editor
->bEmulateVersion10
&& /* v1.0 - 3.0 */
835 cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
836 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
)
838 if (!ME_StreamOutPrint(pStream
, "\\cell "))
841 if (!ME_StreamOutPrint(pStream
, "\\tab "))
844 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDCELL
) {
845 if (pStream
->nNestingLevel
> 1) {
846 if (!ME_StreamOutPrint(pStream
, "\\nestcell "))
849 if (!ME_StreamOutPrint(pStream
, "\\cell "))
853 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
) {
854 if (cursor
.pPara
->member
.para
.pFmt
->dwMask
& PFM_TABLE
&&
855 cursor
.pPara
->member
.para
.pFmt
->wEffects
& PFE_TABLE
&&
856 !(cursor
.pPara
->member
.para
.nFlags
& (MEPF_ROWSTART
|MEPF_ROWEND
|MEPF_CELL
)))
858 if (!ME_StreamOutPrint(pStream
, "\\row \r\n"))
861 if (!ME_StreamOutPrint(pStream
, "\r\n\\par"))
864 /* Skip as many characters as required by current line break */
865 nChars
= max(0, nChars
- cursor
.pRun
->member
.run
.len
);
866 } else if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDROW
) {
867 if (!ME_StreamOutPrint(pStream
, "\\line \r\n"))
873 if (!ME_StreamOutPrint(pStream
, "{"))
875 TRACE("style %p\n", cursor
.pRun
->member
.run
.style
);
876 if (!ME_StreamOutRTFCharProps(pStream
, &cursor
.pRun
->member
.run
.style
->fmt
))
879 nEnd
= (cursor
.pRun
== endCur
.pRun
) ? endCur
.nOffset
: cursor
.pRun
->member
.run
.len
;
880 if (!ME_StreamOutRTFText(pStream
, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
881 nEnd
- cursor
.nOffset
))
884 if (!ME_StreamOutPrint(pStream
, "}"))
887 } while (cursor
.pRun
!= endCur
.pRun
&& ME_NextRun(&cursor
.pPara
, &cursor
.pRun
));
889 if (!ME_StreamOutMove(pStream
, "}\0", 2))
895 static BOOL
ME_StreamOutText(ME_TextEditor
*editor
, ME_OutStream
*pStream
,
896 const ME_Cursor
*start
, int nChars
, DWORD dwFormat
)
898 ME_Cursor cursor
= *start
;
900 UINT nCodePage
= CP_ACP
;
908 if (dwFormat
& SF_USECODEPAGE
)
909 nCodePage
= HIWORD(dwFormat
);
911 /* TODO: Handle SF_TEXTIZED */
913 while (success
&& nChars
&& cursor
.pRun
) {
914 nLen
= min(nChars
, cursor
.pRun
->member
.run
.len
- cursor
.nOffset
);
916 if (!editor
->bEmulateVersion10
&& cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
918 static const WCHAR szEOL
[] = { '\r', '\n' };
920 /* richedit 2.0 - all line breaks are \r\n */
921 if (dwFormat
& SF_UNICODE
)
922 success
= ME_StreamOutMove(pStream
, (const char *)szEOL
, sizeof(szEOL
));
924 success
= ME_StreamOutMove(pStream
, "\r\n", 2);
926 if (dwFormat
& SF_UNICODE
)
927 success
= ME_StreamOutMove(pStream
, (const char *)(get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
)),
928 sizeof(WCHAR
) * nLen
);
932 nSize
= WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
933 nLen
, NULL
, 0, NULL
, NULL
);
934 if (nSize
> nBufLen
) {
936 buffer
= ALLOC_N_OBJ(char, nSize
);
939 WideCharToMultiByte(nCodePage
, 0, get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
),
940 nLen
, buffer
, nSize
, NULL
, NULL
);
941 success
= ME_StreamOutMove(pStream
, buffer
, nSize
);
947 cursor
.pRun
= ME_FindItemFwd(cursor
.pRun
, diRun
);
955 LRESULT
ME_StreamOutRange(ME_TextEditor
*editor
, DWORD dwFormat
,
956 const ME_Cursor
*start
,
957 int nChars
, EDITSTREAM
*stream
)
959 ME_OutStream
*pStream
= ME_StreamOutInit(editor
, stream
);
961 if (dwFormat
& SF_RTF
)
962 ME_StreamOutRTF(editor
, pStream
, start
, nChars
, dwFormat
);
963 else if (dwFormat
& SF_TEXT
|| dwFormat
& SF_TEXTIZED
)
964 ME_StreamOutText(editor
, pStream
, start
, nChars
, dwFormat
);
965 if (!pStream
->stream
->dwError
)
966 ME_StreamOutFlush(pStream
);
967 return ME_StreamOutFree(pStream
);
971 ME_StreamOut(ME_TextEditor
*editor
, DWORD dwFormat
, EDITSTREAM
*stream
)
976 if (dwFormat
& SFF_SELECTION
) {
978 start
= editor
->pCursors
[ME_GetSelectionOfs(editor
, &nStart
, &nTo
)];
979 nChars
= nTo
- nStart
;
981 ME_SetCursorToStart(editor
, &start
);
982 nChars
= ME_GetTextLength(editor
);
983 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
984 if (dwFormat
& SF_RTF
)
987 return ME_StreamOutRange(editor
, dwFormat
, &start
, nChars
, stream
);