win32u: Respect per-monitor thread dpi awareness when getting window from point.
[wine.git] / dlls / riched20 / writer.c
blob70d5290b01e5bcd71ddfe58c7adab4bccd27c8e4
1 /*
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
21 #include "editor.h"
22 #include "rtf.h"
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
26 #define STREAMOUT_BUFFER_SIZE 4096
27 #define STREAMOUT_FONTTBL_SIZE 8192
28 #define STREAMOUT_COLORTBL_SIZE 1024
30 typedef struct tagME_OutStream
32 EDITSTREAM *stream;
33 char buffer[STREAMOUT_BUFFER_SIZE];
34 UINT pos, written;
35 UINT nCodePage;
36 UINT nFontTblLen;
37 ME_FontTableItem fonttbl[STREAMOUT_FONTTBL_SIZE];
38 UINT nColorTblLen;
39 COLORREF colortbl[STREAMOUT_COLORTBL_SIZE];
40 UINT nDefaultFont;
41 UINT nDefaultCodePage;
42 /* nNestingLevel = 0 means we aren't in a cell, 1 means we are in a cell,
43 * an greater numbers mean we are in a cell nested within a cell. */
44 UINT nNestingLevel;
45 CHARFORMAT2W cur_fmt; /* current character format */
46 } ME_OutStream;
48 static BOOL
49 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars);
52 static ME_OutStream*
53 ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream)
55 ME_OutStream *pStream = calloc(1, sizeof(*pStream));
57 pStream->stream = stream;
58 pStream->stream->dwError = 0;
59 pStream->nColorTblLen = 1;
60 pStream->cur_fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
61 pStream->cur_fmt.bUnderlineType = CFU_UNDERLINE;
62 return pStream;
66 static BOOL
67 ME_StreamOutFlush(ME_OutStream *pStream)
69 LONG nWritten = 0;
70 EDITSTREAM *stream = pStream->stream;
72 if (pStream->pos) {
73 TRACE("sending %u bytes\n", pStream->pos);
74 nWritten = pStream->pos;
75 stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer,
76 pStream->pos, &nWritten);
77 TRACE("error=%lu written=%lu\n", stream->dwError, nWritten);
78 if (nWritten == 0 || stream->dwError)
79 return FALSE;
80 /* Don't resend partial chunks if nWritten < pStream->pos */
82 if (nWritten == pStream->pos)
83 pStream->written += nWritten;
84 pStream->pos = 0;
85 return TRUE;
89 static LONG
90 ME_StreamOutFree(ME_OutStream *pStream)
92 LONG written = pStream->written;
93 TRACE("total length = %lu\n", written);
95 free(pStream);
96 return written;
100 static BOOL
101 ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len)
103 while (len) {
104 int space = STREAMOUT_BUFFER_SIZE - pStream->pos;
105 int fit = min(space, len);
107 TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit));
108 memmove(pStream->buffer + pStream->pos, buffer, fit);
109 len -= fit;
110 buffer += fit;
111 pStream->pos += fit;
112 if (pStream->pos == STREAMOUT_BUFFER_SIZE) {
113 if (!ME_StreamOutFlush(pStream))
114 return FALSE;
117 return TRUE;
121 static BOOL WINAPIV
122 ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...)
124 char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */
125 int len;
126 va_list valist;
128 va_start(valist, format);
129 len = vsnprintf(string, sizeof(string), format, valist);
130 va_end(valist);
132 return ME_StreamOutMove(pStream, string, len);
135 #define HEX_BYTES_PER_LINE 40
137 static BOOL
138 ME_StreamOutHexData(ME_OutStream *stream, const BYTE *data, UINT len)
141 char line[HEX_BYTES_PER_LINE * 2 + 1];
142 UINT size, i;
143 static const char hex[] = "0123456789abcdef";
145 while (len)
147 size = min( len, HEX_BYTES_PER_LINE );
148 for (i = 0; i < size; i++)
150 line[i * 2] = hex[(*data >> 4) & 0xf];
151 line[i * 2 + 1] = hex[*data & 0xf];
152 data++;
154 line[size * 2] = '\n';
155 if (!ME_StreamOutMove( stream, line, size * 2 + 1 ))
156 return FALSE;
157 len -= size;
159 return TRUE;
162 static BOOL
163 ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat)
165 const char *cCharSet = NULL;
166 UINT nCodePage;
167 LANGID language;
168 BOOL success;
170 if (dwFormat & SF_USECODEPAGE) {
171 CPINFOEXW info;
173 switch (HIWORD(dwFormat)) {
174 case CP_ACP:
175 cCharSet = "ansi";
176 nCodePage = GetACP();
177 break;
178 case CP_OEMCP:
179 nCodePage = GetOEMCP();
180 if (nCodePage == 437)
181 cCharSet = "pc";
182 else if (nCodePage == 850)
183 cCharSet = "pca";
184 else
185 cCharSet = "ansi";
186 break;
187 case CP_UTF8:
188 nCodePage = CP_UTF8;
189 break;
190 default:
191 if (HIWORD(dwFormat) == CP_MACCP) {
192 cCharSet = "mac";
193 nCodePage = 10000; /* MacRoman */
194 } else {
195 cCharSet = "ansi";
196 nCodePage = 1252; /* Latin-1 */
198 if (GetCPInfoExW(HIWORD(dwFormat), 0, &info))
199 nCodePage = info.CodePage;
201 } else {
202 cCharSet = "ansi";
203 /* TODO: If the original document contained an \ansicpg value, retain it.
204 * Otherwise, M$ richedit emits a codepage number determined from the
205 * charset of the default font here. Anyway, this value is not used by
206 * the reader... */
207 nCodePage = GetACP();
209 if (nCodePage == CP_UTF8)
210 success = ME_StreamOutPrint(pStream, "{\\urtf");
211 else
212 success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage);
214 if (!success)
215 return FALSE;
217 pStream->nDefaultCodePage = nCodePage;
219 /* FIXME: This should be a document property */
220 /* TODO: handle SFF_PLAINRTF */
221 language = GetUserDefaultLangID();
222 if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language))
223 return FALSE;
225 /* FIXME: This should be a document property */
226 pStream->nDefaultFont = 0;
228 return TRUE;
231 static void add_font_to_fonttbl( ME_OutStream *stream, ME_Style *style )
233 ME_FontTableItem *table = stream->fonttbl;
234 CHARFORMAT2W *fmt = &style->fmt;
235 WCHAR *face = fmt->szFaceName;
236 BYTE charset = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET;
237 int i;
239 if (fmt->dwMask & CFM_FACE)
241 for (i = 0; i < stream->nFontTblLen; i++)
242 if (table[i].bCharSet == charset
243 && (table[i].szFaceName == face || !wcscmp(table[i].szFaceName, face)))
244 break;
246 if (i == stream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE)
248 table[i].bCharSet = charset;
249 table[i].szFaceName = face;
250 stream->nFontTblLen++;
255 static BOOL find_font_in_fonttbl( ME_OutStream *stream, CHARFORMAT2W *fmt, unsigned int *idx )
257 WCHAR *facename;
258 int i;
260 *idx = 0;
261 if (fmt->dwMask & CFM_FACE)
262 facename = fmt->szFaceName;
263 else
264 facename = stream->fonttbl[0].szFaceName;
265 for (i = 0; i < stream->nFontTblLen; i++)
267 if (facename == stream->fonttbl[i].szFaceName
268 || !wcscmp(facename, stream->fonttbl[i].szFaceName))
269 if (!(fmt->dwMask & CFM_CHARSET)
270 || fmt->bCharSet == stream->fonttbl[i].bCharSet)
272 *idx = i;
273 break;
277 return i < stream->nFontTblLen;
280 static void add_color_to_colortbl( ME_OutStream *stream, COLORREF color )
282 int i;
284 for (i = 1; i < stream->nColorTblLen; i++)
285 if (stream->colortbl[i] == color)
286 break;
288 if (i == stream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE)
290 stream->colortbl[i] = color;
291 stream->nColorTblLen++;
295 static BOOL find_color_in_colortbl( ME_OutStream *stream, COLORREF color, unsigned int *idx )
297 int i;
299 *idx = 0;
300 for (i = 1; i < stream->nColorTblLen; i++)
302 if (stream->colortbl[i] == color)
304 *idx = i;
305 break;
309 return i < stream->nFontTblLen;
312 static BOOL stream_out_font_and_colour_tbls( ME_OutStream *pStream, ME_Run *first, ME_Run *last )
314 ME_Run *run = first;
315 ME_FontTableItem *table = pStream->fonttbl;
316 unsigned int i;
317 ME_Cell *cell = NULL;
318 ME_Paragraph *prev_para = NULL;
322 CHARFORMAT2W *fmt = &run->style->fmt;
324 add_font_to_fonttbl( pStream, run->style );
326 if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR))
327 add_color_to_colortbl( pStream, fmt->crTextColor );
328 if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR))
329 add_color_to_colortbl( pStream, fmt->crBackColor );
331 if (run->para != prev_para)
333 /* check for any para numbering text */
334 if (run->para->fmt.wNumbering)
335 add_font_to_fonttbl( pStream, run->para->para_num.style );
337 if ((cell = para_cell( run->para )))
339 ME_Border* borders[4] = { &cell->border.top, &cell->border.left,
340 &cell->border.bottom, &cell->border.right };
341 for (i = 0; i < 4; i++)
342 if (borders[i]->width > 0)
343 add_color_to_colortbl( pStream, borders[i]->colorRef );
346 prev_para = run->para;
349 if (run == last) break;
350 run = run_next_all_paras( run );
351 } while (run);
353 if (!ME_StreamOutPrint(pStream, "{\\fonttbl"))
354 return FALSE;
356 for (i = 0; i < pStream->nFontTblLen; i++) {
357 if (table[i].bCharSet != DEFAULT_CHARSET) {
358 if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet))
359 return FALSE;
360 } else {
361 if (!ME_StreamOutPrint(pStream, "{\\f%u ", i))
362 return FALSE;
364 if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1))
365 return FALSE;
366 if (!ME_StreamOutPrint(pStream, ";}"))
367 return FALSE;
369 if (!ME_StreamOutPrint(pStream, "}\r\n"))
370 return FALSE;
372 /* Output the color table */
373 if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */
374 for (i = 1; i < pStream->nColorTblLen; i++)
376 if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF,
377 (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF))
378 return FALSE;
380 if (!ME_StreamOutPrint(pStream, "}\r\n")) return FALSE;
382 return TRUE;
385 static BOOL stream_out_table_props( ME_TextEditor *editor, ME_OutStream *pStream,
386 ME_Paragraph *para )
388 ME_Cell *cell;
389 char props[STREAMOUT_BUFFER_SIZE] = "";
390 int i;
391 const char sideChar[4] = {'t','l','b','r'};
393 if (!ME_StreamOutPrint(pStream, "\\trowd"))
394 return FALSE;
395 if (!editor->bEmulateVersion10) /* v4.1 */
397 PARAFORMAT2 *pFmt = &table_row_end( para )->fmt;
398 cell = table_row_first_cell( para );
399 assert( cell );
400 if (pFmt->dxOffset)
401 sprintf(props + strlen(props), "\\trgaph%ld", pFmt->dxOffset);
402 if (pFmt->dxStartIndent)
403 sprintf(props + strlen(props), "\\trleft%ld", pFmt->dxStartIndent);
406 ME_Border* borders[4] = { &cell->border.top, &cell->border.left,
407 &cell->border.bottom, &cell->border.right };
408 for (i = 0; i < 4; i++)
410 if (borders[i]->width)
412 unsigned int idx;
413 COLORREF crColor = borders[i]->colorRef;
414 sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]);
415 sprintf(props + strlen(props), "\\brdrs");
416 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
417 if (find_color_in_colortbl( pStream, crColor, &idx ))
418 sprintf(props + strlen(props), "\\brdrcf%u", idx);
421 sprintf( props + strlen(props), "\\cellx%d", cell->nRightBoundary );
422 cell = cell_next( cell );
423 } while (cell_next( cell ));
425 else /* v1.0 - 3.0 */
427 const ME_Border* borders[4] = { &para->border.top,
428 &para->border.left,
429 &para->border.bottom,
430 &para->border.right };
431 PARAFORMAT2 *pFmt = &para->fmt;
433 assert( !(para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND | MEPF_CELL)) );
434 if (pFmt->dxOffset)
435 sprintf(props + strlen(props), "\\trgaph%ld", pFmt->dxOffset);
436 if (pFmt->dxStartIndent)
437 sprintf(props + strlen(props), "\\trleft%ld", pFmt->dxStartIndent);
438 for (i = 0; i < 4; i++)
440 if (borders[i]->width)
442 unsigned int idx;
443 COLORREF crColor = borders[i]->colorRef;
444 sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]);
445 sprintf(props + strlen(props), "\\brdrs");
446 sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width);
447 if (find_color_in_colortbl( pStream, crColor, &idx ))
448 sprintf(props + strlen(props), "\\brdrcf%u", idx);
451 for (i = 0; i < pFmt->cTabCount; i++)
453 sprintf(props + strlen(props), "\\cellx%ld", pFmt->rgxTabs[i] & 0x00FFFFFF);
456 if (!ME_StreamOutPrint(pStream, props))
457 return FALSE;
458 props[0] = '\0';
459 return TRUE;
462 static BOOL stream_out_para_num( ME_OutStream *stream, ME_Paragraph *para, BOOL pn_dest )
464 static const char fmt_label[] = "{\\*\\pn\\pnlvlbody\\pnf%u\\pnindent%d\\pnstart%d%s%s}";
465 static const char fmt_bullet[] = "{\\*\\pn\\pnlvlblt\\pnf%u\\pnindent%d{\\pntxtb\\'b7}}";
466 static const char dec[] = "\\pndec";
467 static const char lcltr[] = "\\pnlcltr";
468 static const char ucltr[] = "\\pnucltr";
469 static const char lcrm[] = "\\pnlcrm";
470 static const char ucrm[] = "\\pnucrm";
471 static const char period[] = "{\\pntxta.}";
472 static const char paren[] = "{\\pntxta)}";
473 static const char parens[] = "{\\pntxtb(}{\\pntxta)}";
474 const char *type, *style = "";
475 unsigned int idx;
477 find_font_in_fonttbl( stream, &para->para_num.style->fmt, &idx );
479 if (!ME_StreamOutPrint( stream, "{\\pntext\\f%u ", idx )) return FALSE;
480 if (!ME_StreamOutRTFText( stream, para->para_num.text->szData, para->para_num.text->nLen ))
481 return FALSE;
482 if (!ME_StreamOutPrint( stream, "\\tab}" )) return FALSE;
484 if (!pn_dest) return TRUE;
486 if (para->fmt.wNumbering == PFN_BULLET)
488 if (!ME_StreamOutPrint( stream, fmt_bullet, idx, para->fmt.wNumberingTab ))
489 return FALSE;
491 else
493 switch (para->fmt.wNumbering)
495 case PFN_ARABIC:
496 default:
497 type = dec;
498 break;
499 case PFN_LCLETTER:
500 type = lcltr;
501 break;
502 case PFN_UCLETTER:
503 type = ucltr;
504 break;
505 case PFN_LCROMAN:
506 type = lcrm;
507 break;
508 case PFN_UCROMAN:
509 type = ucrm;
510 break;
512 switch (para->fmt.wNumberingStyle & 0xf00)
514 case PFNS_PERIOD:
515 style = period;
516 break;
517 case PFNS_PAREN:
518 style = paren;
519 break;
520 case PFNS_PARENS:
521 style = parens;
522 break;
525 if (!ME_StreamOutPrint( stream, fmt_label, idx, para->fmt.wNumberingTab,
526 para->fmt.wNumberingStart, type, style ))
527 return FALSE;
529 return TRUE;
532 static BOOL stream_out_para_props( ME_TextEditor *editor, ME_OutStream *pStream,
533 ME_Paragraph *para )
535 PARAFORMAT2 *fmt = &para->fmt;
536 char props[STREAMOUT_BUFFER_SIZE] = "";
537 int i;
538 ME_Paragraph *prev_para = para_prev( para );
540 if (!editor->bEmulateVersion10) /* v4.1 */
542 if (para->nFlags & MEPF_ROWSTART)
544 pStream->nNestingLevel++;
545 if (pStream->nNestingLevel == 1)
546 if (!stream_out_table_props( editor, pStream, para )) return FALSE;
547 return TRUE;
549 else if (para->nFlags & MEPF_ROWEND)
551 pStream->nNestingLevel--;
552 if (pStream->nNestingLevel >= 1)
554 if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops")) return FALSE;
555 if (!stream_out_table_props( editor, pStream, para )) return FALSE;
556 if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n")) return FALSE;
558 else if (!ME_StreamOutPrint(pStream, "\\row\r\n")) return FALSE;
559 return TRUE;
562 else /* v1.0 - 3.0 */
564 if (para->fmt.dwMask & PFM_TABLE && para->fmt.wEffects & PFE_TABLE)
565 if (!stream_out_table_props( editor, pStream, para )) return FALSE;
568 if (prev_para && !memcmp( fmt, &prev_para->fmt, sizeof(*fmt) ))
570 if (fmt->wNumbering)
571 return stream_out_para_num( pStream, para, FALSE );
572 return TRUE;
575 if (!ME_StreamOutPrint(pStream, "\\pard"))
576 return FALSE;
578 if (fmt->wNumbering)
579 if (!stream_out_para_num( pStream, para, TRUE )) return FALSE;
581 if (!editor->bEmulateVersion10) /* v4.1 */
583 if (pStream->nNestingLevel > 0) strcat(props, "\\intbl");
584 if (pStream->nNestingLevel > 1) sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel);
586 else /* v1.0 - 3.0 */
588 if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)
589 strcat(props, "\\intbl");
592 /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and
593 * when streaming border keywords in, PFM_BORDER is set, but wBorder field is
594 * set very different from the documentation.
595 * (Tested with RichEdit 5.50.25.0601) */
597 if (fmt->dwMask & PFM_ALIGNMENT)
599 switch (fmt->wAlignment)
601 case PFA_LEFT:
602 /* Default alignment: not emitted */
603 break;
604 case PFA_RIGHT:
605 strcat(props, "\\qr");
606 break;
607 case PFA_CENTER:
608 strcat(props, "\\qc");
609 break;
610 case PFA_JUSTIFY:
611 strcat(props, "\\qj");
612 break;
616 if (fmt->dwMask & PFM_LINESPACING)
618 /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the
619 * PFM_SPACEAFTER flag. Is that true? I don't believe so. */
620 switch (fmt->bLineSpacingRule)
622 case 0: /* Single spacing */
623 strcat(props, "\\sl-240\\slmult1");
624 break;
625 case 1: /* 1.5 spacing */
626 strcat(props, "\\sl-360\\slmult1");
627 break;
628 case 2: /* Double spacing */
629 strcat(props, "\\sl-480\\slmult1");
630 break;
631 case 3:
632 sprintf(props + strlen(props), "\\sl%ld\\slmult0", fmt->dyLineSpacing);
633 break;
634 case 4:
635 sprintf(props + strlen(props), "\\sl-%ld\\slmult0", fmt->dyLineSpacing);
636 break;
637 case 5:
638 sprintf(props + strlen(props), "\\sl-%ld\\slmult1", fmt->dyLineSpacing * 240 / 20);
639 break;
643 if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN)
644 strcat(props, "\\hyph0");
645 if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP)
646 strcat(props, "\\keep");
647 if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT)
648 strcat(props, "\\keepn");
649 if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER)
650 strcat(props, "\\noline");
651 if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL)
652 strcat(props, "\\nowidctlpar");
653 if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE)
654 strcat(props, "\\pagebb");
655 if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA)
656 strcat(props, "\\rtlpar");
657 if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE)
658 strcat(props, "\\sbys");
660 if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */
661 fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE))
663 if (fmt->dxOffset)
664 sprintf(props + strlen(props), "\\li%ld", fmt->dxOffset);
665 if (fmt->dxStartIndent)
666 sprintf(props + strlen(props), "\\fi%ld", fmt->dxStartIndent);
667 if (fmt->dxRightIndent)
668 sprintf(props + strlen(props), "\\ri%ld", fmt->dxRightIndent);
669 if (fmt->dwMask & PFM_TABSTOPS) {
670 static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" };
672 for (i = 0; i < fmt->cTabCount; i++)
674 switch ((fmt->rgxTabs[i] >> 24) & 0xf)
676 case 1:
677 strcat(props, "\\tqc");
678 break;
679 case 2:
680 strcat(props, "\\tqr");
681 break;
682 case 3:
683 strcat(props, "\\tqdec");
684 break;
685 case 4:
686 /* Word bar tab (vertical bar). Handled below */
687 break;
689 if (fmt->rgxTabs[i] >> 28 <= 5)
690 strcat(props, leader[fmt->rgxTabs[i] >> 28]);
691 sprintf(props+strlen(props), "\\tx%ld", fmt->rgxTabs[i]&0x00FFFFFF);
695 if (fmt->dySpaceAfter)
696 sprintf(props + strlen(props), "\\sa%ld", fmt->dySpaceAfter);
697 if (fmt->dySpaceBefore)
698 sprintf(props + strlen(props), "\\sb%ld", fmt->dySpaceBefore);
699 if (fmt->sStyle != -1)
700 sprintf(props + strlen(props), "\\s%d", fmt->sStyle);
702 if (fmt->dwMask & PFM_SHADING)
704 static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag",
705 "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross",
706 "\\bghoriz", "\\bgvert", "\\bgfdiag",
707 "\\bgbdiag", "\\bgcross", "\\bgdcross",
708 "", "", "" };
709 if (fmt->wShadingWeight)
710 sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight);
711 if (fmt->wShadingStyle & 0xF)
712 strcat(props, style[fmt->wShadingStyle & 0xF]);
713 if ((fmt->wShadingStyle >> 4) & 0xf)
714 sprintf(props + strlen(props), "\\cfpat%d", (fmt->wShadingStyle >> 4) & 0xf);
715 if ((fmt->wShadingStyle >> 8) & 0xf)
716 sprintf(props + strlen(props), "\\cbpat%d", (fmt->wShadingStyle >> 8) & 0xf);
718 if (*props)
719 strcat(props, " ");
721 if (*props && !ME_StreamOutPrint(pStream, props))
722 return FALSE;
724 return TRUE;
728 static BOOL
729 ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt)
731 char props[STREAMOUT_BUFFER_SIZE] = "";
732 unsigned int i;
733 CHARFORMAT2W *old_fmt = &pStream->cur_fmt;
734 static const struct
736 DWORD effect;
737 const char *on, *off;
738 } effects[] =
740 { CFE_ALLCAPS, "\\caps", "\\caps0" },
741 { CFE_BOLD, "\\b", "\\b0" },
742 { CFE_DISABLED, "\\disabled", "\\disabled0" },
743 { CFE_EMBOSS, "\\embo", "\\embo0" },
744 { CFE_HIDDEN, "\\v", "\\v0" },
745 { CFE_IMPRINT, "\\impr", "\\impr0" },
746 { CFE_ITALIC, "\\i", "\\i0" },
747 { CFE_OUTLINE, "\\outl", "\\outl0" },
748 { CFE_PROTECTED, "\\protect", "\\protect0" },
749 { CFE_SHADOW, "\\shad", "\\shad0" },
750 { CFE_SMALLCAPS, "\\scaps", "\\scaps0" },
751 { CFE_STRIKEOUT, "\\strike", "\\strike0" },
754 for (i = 0; i < ARRAY_SIZE( effects ); i++)
756 if ((old_fmt->dwEffects ^ fmt->dwEffects) & effects[i].effect)
757 strcat( props, fmt->dwEffects & effects[i].effect ? effects[i].on : effects[i].off );
760 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOBACKCOLOR ||
761 (!(fmt->dwEffects & CFE_AUTOBACKCOLOR) && old_fmt->crBackColor != fmt->crBackColor))
763 if (fmt->dwEffects & CFE_AUTOBACKCOLOR) i = 0;
764 else find_color_in_colortbl( pStream, fmt->crBackColor, &i );
765 sprintf(props + strlen(props), "\\highlight%u", i);
767 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOCOLOR ||
768 (!(fmt->dwEffects & CFE_AUTOCOLOR) && old_fmt->crTextColor != fmt->crTextColor))
770 if (fmt->dwEffects & CFE_AUTOCOLOR) i = 0;
771 else find_color_in_colortbl( pStream, fmt->crTextColor, &i );
772 sprintf(props + strlen(props), "\\cf%u", i);
775 if (old_fmt->bAnimation != fmt->bAnimation)
776 sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation);
777 if (old_fmt->wKerning != fmt->wKerning)
778 sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning);
780 if (old_fmt->lcid != fmt->lcid)
782 /* TODO: handle SFF_PLAINRTF */
783 if (LOWORD(fmt->lcid) == 1024)
784 strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024");
785 else
786 sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid));
789 if (old_fmt->yOffset != fmt->yOffset)
791 if (fmt->yOffset >= 0)
792 sprintf(props + strlen(props), "\\up%ld", fmt->yOffset);
793 else
794 sprintf(props + strlen(props), "\\dn%ld", -fmt->yOffset);
796 if (old_fmt->yHeight != fmt->yHeight)
797 sprintf(props + strlen(props), "\\fs%ld", fmt->yHeight / 10);
798 if (old_fmt->sSpacing != fmt->sSpacing)
799 sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing);
800 if ((old_fmt->dwEffects ^ fmt->dwEffects) & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT))
802 if (fmt->dwEffects & CFE_SUBSCRIPT)
803 strcat(props, "\\sub");
804 else if (fmt->dwEffects & CFE_SUPERSCRIPT)
805 strcat(props, "\\super");
806 else
807 strcat(props, "\\nosupersub");
809 if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_UNDERLINE ||
810 old_fmt->bUnderlineType != fmt->bUnderlineType)
812 BYTE type = (fmt->dwEffects & CFE_UNDERLINE) ? fmt->bUnderlineType : CFU_UNDERLINENONE;
813 switch (type)
815 case CFU_UNDERLINE:
816 strcat(props, "\\ul");
817 break;
818 case CFU_UNDERLINEDOTTED:
819 strcat(props, "\\uld");
820 break;
821 case CFU_UNDERLINEDOUBLE:
822 strcat(props, "\\uldb");
823 break;
824 case CFU_UNDERLINEWORD:
825 strcat(props, "\\ulw");
826 break;
827 case CFU_CF1UNDERLINE:
828 case CFU_UNDERLINENONE:
829 default:
830 strcat(props, "\\ulnone");
831 break;
835 if (wcscmp(old_fmt->szFaceName, fmt->szFaceName) ||
836 old_fmt->bCharSet != fmt->bCharSet)
838 if (find_font_in_fonttbl( pStream, fmt, &i ))
840 sprintf(props + strlen(props), "\\f%u", i);
842 /* In UTF-8 mode, charsets/codepages are not used */
843 if (pStream->nDefaultCodePage != CP_UTF8)
845 if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET)
846 pStream->nCodePage = pStream->nDefaultCodePage;
847 else
848 pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet);
852 if (*props)
853 strcat(props, " ");
854 if (!ME_StreamOutPrint(pStream, props))
855 return FALSE;
856 *old_fmt = *fmt;
857 return TRUE;
861 static BOOL
862 ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars)
864 char buffer[STREAMOUT_BUFFER_SIZE];
865 int pos = 0;
866 int fit, nBytes, i;
868 if (nChars == -1)
869 nChars = lstrlenW(text);
871 while (nChars) {
872 /* In UTF-8 mode, font charsets are not used. */
873 if (pStream->nDefaultCodePage == CP_UTF8) {
874 /* 6 is the maximum character length in UTF-8 */
875 fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6);
876 nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer,
877 STREAMOUT_BUFFER_SIZE, NULL, NULL);
878 nChars -= fit;
879 text += fit;
880 for (i = 0; i < nBytes; i++)
881 if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') {
882 if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos))
883 return FALSE;
884 pos = i;
886 if (pos < nBytes)
887 if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos))
888 return FALSE;
889 pos = 0;
890 } else if (*text < 128) {
891 if (*text == '{' || *text == '}' || *text == '\\')
892 buffer[pos++] = '\\';
893 buffer[pos++] = (char)(*text++);
894 nChars--;
895 } else {
896 BOOL unknown = FALSE;
897 char letter[3];
899 /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of
900 * codepages including CP_SYMBOL for which the last parameter must be set
901 * to NULL for the function to succeed. But in Wine we need to care only
902 * about CP_SYMBOL */
903 nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1,
904 letter, 3, NULL,
905 (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown);
906 if (unknown)
907 pos += sprintf(buffer + pos, "\\u%d?", (short)*text);
908 else if ((BYTE)*letter < 128) {
909 if (*letter == '{' || *letter == '}' || *letter == '\\')
910 buffer[pos++] = '\\';
911 buffer[pos++] = *letter;
912 } else {
913 for (i = 0; i < nBytes; i++)
914 pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]);
916 text++;
917 nChars--;
919 if (pos >= STREAMOUT_BUFFER_SIZE - 11) {
920 if (!ME_StreamOutMove(pStream, buffer, pos))
921 return FALSE;
922 pos = 0;
925 return ME_StreamOutMove(pStream, buffer, pos);
928 static BOOL stream_out_graphics( ME_TextEditor *editor, ME_OutStream *stream,
929 ME_Run *run )
931 IDataObject *data;
932 HRESULT hr;
933 FORMATETC fmt = { CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF };
934 STGMEDIUM med = { TYMED_NULL };
935 BOOL ret = FALSE;
936 ENHMETAHEADER *emf_bits = NULL;
937 UINT size;
938 SIZE goal, pic;
939 ME_Context c;
940 HDC hdc;
942 hr = IOleObject_QueryInterface( run->reobj->obj.poleobj, &IID_IDataObject, (void **)&data );
943 if (FAILED(hr)) return FALSE;
945 hdc = ITextHost_TxGetDC( editor->texthost );
946 ME_InitContext( &c, editor, hdc );
947 hr = IDataObject_QueryGetData( data, &fmt );
948 if (hr != S_OK) goto done;
950 hr = IDataObject_GetData( data, &fmt, &med );
951 if (FAILED(hr)) goto done;
952 if (med.tymed != TYMED_ENHMF) goto done;
954 size = GetEnhMetaFileBits( med.hEnhMetaFile, 0, NULL );
955 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
957 emf_bits = malloc( size );
958 if (!emf_bits) goto done;
960 size = GetEnhMetaFileBits( med.hEnhMetaFile, size, (BYTE *)emf_bits );
961 if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done;
963 /* size_in_pixels = (frame_size / 100) * szlDevice / szlMillimeters
964 pic = size_in_pixels * 2540 / dpi */
965 pic.cx = MulDiv( emf_bits->rclFrame.right - emf_bits->rclFrame.left, emf_bits->szlDevice.cx * 254,
966 emf_bits->szlMillimeters.cx * c.dpi.cx * 10 );
967 pic.cy = MulDiv( emf_bits->rclFrame.bottom - emf_bits->rclFrame.top, emf_bits->szlDevice.cy * 254,
968 emf_bits->szlMillimeters.cy * c.dpi.cy * 10 );
970 /* convert goal size to twips */
971 goal.cx = MulDiv( run->reobj->obj.sizel.cx, 144, 254 );
972 goal.cy = MulDiv( run->reobj->obj.sizel.cy, 144, 254 );
974 if (!ME_StreamOutPrint( stream, "{\\*\\shppict{\\pict\\emfblip\\picw%d\\pich%d\\picwgoal%d\\pichgoal%d\n",
975 pic.cx, pic.cy, goal.cx, goal.cy ))
976 goto done;
978 if (!ME_StreamOutHexData( stream, (BYTE *)emf_bits, size ))
979 goto done;
981 if (!ME_StreamOutPrint( stream, "}}\n" ))
982 goto done;
984 ret = TRUE;
986 done:
987 ME_DestroyContext( &c );
988 ITextHost_TxReleaseDC( editor->texthost, hdc );
989 free( emf_bits );
990 ReleaseStgMedium( &med );
991 IDataObject_Release( data );
992 return ret;
995 static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream,
996 const ME_Cursor *start, int nChars, int dwFormat)
998 ME_Cursor cursor = *start;
999 ME_Paragraph *prev_para = NULL;
1000 ME_Cursor endCur = cursor;
1002 ME_MoveCursorChars(editor, &endCur, nChars, TRUE);
1004 if (!ME_StreamOutRTFHeader(pStream, dwFormat))
1005 return FALSE;
1007 if (!stream_out_font_and_colour_tbls( pStream, cursor.run, endCur.run ))
1008 return FALSE;
1010 /* TODO: stylesheet table */
1012 if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0;}\r\n"))
1013 return FALSE;
1015 /* TODO: information group */
1017 /* TODO: document formatting properties */
1019 /* FIXME: We have only one document section */
1021 /* TODO: section formatting properties */
1025 if (cursor.para != prev_para)
1027 prev_para = cursor.para;
1028 if (!stream_out_para_props( editor, pStream, cursor.para ))
1029 return FALSE;
1032 if (cursor.run == endCur.run && !endCur.nOffset)
1033 break;
1035 TRACE("flags %xh\n", cursor.run->nFlags);
1036 /* TODO: emit embedded objects */
1037 if (cursor.para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1038 continue;
1039 if (cursor.run->nFlags & MERF_GRAPHICS)
1041 if (!stream_out_graphics( editor, pStream, cursor.run ))
1042 return FALSE;
1044 else if (cursor.run->nFlags & MERF_TAB)
1046 if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */
1047 para_in_table( cursor.para ))
1049 if (!ME_StreamOutPrint(pStream, "\\cell "))
1050 return FALSE;
1052 else
1054 if (!ME_StreamOutPrint(pStream, "\\tab "))
1055 return FALSE;
1058 else if (cursor.run->nFlags & MERF_ENDCELL)
1060 if (pStream->nNestingLevel > 1)
1062 if (!ME_StreamOutPrint(pStream, "\\nestcell "))
1063 return FALSE;
1065 else
1067 if (!ME_StreamOutPrint(pStream, "\\cell "))
1068 return FALSE;
1070 nChars--;
1072 else if (cursor.run->nFlags & MERF_ENDPARA)
1074 if (!ME_StreamOutRTFCharProps( pStream, &cursor.run->style->fmt ))
1075 return FALSE;
1077 if (para_in_table( cursor.para ) &&
1078 !(cursor.para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND | MEPF_CELL)))
1080 if (!ME_StreamOutPrint(pStream, "\\row\r\n"))
1081 return FALSE;
1083 else
1085 if (!ME_StreamOutPrint(pStream, "\\par\r\n"))
1086 return FALSE;
1088 /* Skip as many characters as required by current line break */
1089 nChars = max(0, nChars - cursor.run->len);
1091 else if (cursor.run->nFlags & MERF_ENDROW)
1093 if (!ME_StreamOutPrint(pStream, "\\line\r\n"))
1094 return FALSE;
1095 nChars--;
1097 else
1099 int nEnd;
1101 TRACE("style %p\n", cursor.run->style);
1102 if (!ME_StreamOutRTFCharProps( pStream, &cursor.run->style->fmt ))
1103 return FALSE;
1105 nEnd = (cursor.run == endCur.run) ? endCur.nOffset : cursor.run->len;
1106 if (!ME_StreamOutRTFText(pStream, get_text( cursor.run, cursor.nOffset ),
1107 nEnd - cursor.nOffset))
1108 return FALSE;
1109 cursor.nOffset = 0;
1111 } while (cursor.run != endCur.run && cursor_next_run( &cursor, TRUE ));
1113 if (!ME_StreamOutMove(pStream, "}\0", 2))
1114 return FALSE;
1115 return TRUE;
1119 static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream,
1120 const ME_Cursor *start, int nChars, DWORD dwFormat)
1122 ME_Cursor cursor = *start;
1123 int nLen;
1124 UINT nCodePage = CP_ACP;
1125 char *buffer = NULL;
1126 int nBufLen = 0;
1127 BOOL success = TRUE;
1129 if (!cursor.run)
1130 return FALSE;
1132 if (dwFormat & SF_USECODEPAGE)
1133 nCodePage = HIWORD(dwFormat);
1135 /* TODO: Handle SF_TEXTIZED */
1137 while (success && nChars && cursor.run)
1139 nLen = min(nChars, cursor.run->len - cursor.nOffset);
1141 if (!editor->bEmulateVersion10 && cursor.run->nFlags & MERF_ENDPARA)
1143 /* richedit 2.0 - all line breaks are \r\n */
1144 if (dwFormat & SF_UNICODE)
1145 success = ME_StreamOutMove(pStream, (const char *)L"\r\n", 2 * sizeof(WCHAR));
1146 else
1147 success = ME_StreamOutMove(pStream, "\r\n", 2);
1148 } else {
1149 if (dwFormat & SF_UNICODE)
1150 success = ME_StreamOutMove(pStream, (const char *)(get_text( cursor.run, cursor.nOffset )),
1151 sizeof(WCHAR) * nLen);
1152 else {
1153 int nSize;
1155 nSize = WideCharToMultiByte(nCodePage, 0, get_text( cursor.run, cursor.nOffset ),
1156 nLen, NULL, 0, NULL, NULL);
1157 if (nSize > nBufLen) {
1158 buffer = realloc(buffer, nSize);
1159 nBufLen = nSize;
1161 WideCharToMultiByte(nCodePage, 0, get_text( cursor.run, cursor.nOffset ),
1162 nLen, buffer, nSize, NULL, NULL);
1163 success = ME_StreamOutMove(pStream, buffer, nSize);
1167 nChars -= nLen;
1168 cursor.nOffset = 0;
1169 cursor.run = run_next_all_paras( cursor.run );
1172 free(buffer);
1173 return success;
1177 LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat,
1178 const ME_Cursor *start,
1179 int nChars, EDITSTREAM *stream)
1181 ME_OutStream *pStream = ME_StreamOutInit(editor, stream);
1183 if (dwFormat & SF_RTF)
1184 ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat);
1185 else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED)
1186 ME_StreamOutText(editor, pStream, start, nChars, dwFormat);
1187 if (!pStream->stream->dwError)
1188 ME_StreamOutFlush(pStream);
1189 return ME_StreamOutFree(pStream);
1192 LRESULT
1193 ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream)
1195 ME_Cursor start;
1196 int nChars;
1198 if (dwFormat & SFF_SELECTION) {
1199 LONG nStart, nTo;
1200 start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)];
1201 nChars = nTo - nStart;
1202 } else {
1203 ME_SetCursorToStart(editor, &start);
1204 nChars = ME_GetTextLength(editor);
1205 /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */
1206 if (dwFormat & SF_RTF)
1207 nChars++;
1209 return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream);