| /* |
| * RichEdit - RTF writer module |
| * |
| * Copyright 2005 by Phil Krylov |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include "config.h" |
| #include "wine/port.h" |
| |
| #define NONAMELESSUNION |
| |
| #include "editor.h" |
| #include "rtf.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(richedit); |
| |
| #define STREAMOUT_BUFFER_SIZE 4096 |
| #define STREAMOUT_FONTTBL_SIZE 8192 |
| #define STREAMOUT_COLORTBL_SIZE 1024 |
| |
| typedef struct tagME_OutStream |
| { |
| EDITSTREAM *stream; |
| char buffer[STREAMOUT_BUFFER_SIZE]; |
| UINT pos, written; |
| UINT nCodePage; |
| UINT nFontTblLen; |
| ME_FontTableItem fonttbl[STREAMOUT_FONTTBL_SIZE]; |
| UINT nColorTblLen; |
| COLORREF colortbl[STREAMOUT_COLORTBL_SIZE]; |
| UINT nDefaultFont; |
| UINT nDefaultCodePage; |
| /* nNestingLevel = 0 means we aren't in a cell, 1 means we are in a cell, |
| * an greater numbers mean we are in a cell nested within a cell. */ |
| UINT nNestingLevel; |
| CHARFORMAT2W cur_fmt; /* current character format */ |
| } ME_OutStream; |
| |
| static BOOL |
| ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars); |
| |
| |
| static ME_OutStream* |
| ME_StreamOutInit(ME_TextEditor *editor, EDITSTREAM *stream) |
| { |
| ME_OutStream *pStream = ALLOC_OBJ(ME_OutStream); |
| pStream->stream = stream; |
| pStream->stream->dwError = 0; |
| pStream->pos = 0; |
| pStream->written = 0; |
| pStream->nFontTblLen = 0; |
| pStream->nColorTblLen = 1; |
| pStream->nNestingLevel = 0; |
| memset(&pStream->cur_fmt, 0, sizeof(pStream->cur_fmt)); |
| pStream->cur_fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR; |
| pStream->cur_fmt.bUnderlineType = CFU_UNDERLINE; |
| return pStream; |
| } |
| |
| |
| static BOOL |
| ME_StreamOutFlush(ME_OutStream *pStream) |
| { |
| LONG nWritten = 0; |
| EDITSTREAM *stream = pStream->stream; |
| |
| if (pStream->pos) { |
| TRACE("sending %u bytes\n", pStream->pos); |
| nWritten = pStream->pos; |
| stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer, |
| pStream->pos, &nWritten); |
| TRACE("error=%u written=%u\n", stream->dwError, nWritten); |
| if (nWritten == 0 || stream->dwError) |
| return FALSE; |
| /* Don't resend partial chunks if nWritten < pStream->pos */ |
| } |
| if (nWritten == pStream->pos) |
| pStream->written += nWritten; |
| pStream->pos = 0; |
| return TRUE; |
| } |
| |
| |
| static LONG |
| ME_StreamOutFree(ME_OutStream *pStream) |
| { |
| LONG written = pStream->written; |
| TRACE("total length = %u\n", written); |
| |
| FREE_OBJ(pStream); |
| return written; |
| } |
| |
| |
| static BOOL |
| ME_StreamOutMove(ME_OutStream *pStream, const char *buffer, int len) |
| { |
| while (len) { |
| int space = STREAMOUT_BUFFER_SIZE - pStream->pos; |
| int fit = min(space, len); |
| |
| TRACE("%u:%u:%s\n", pStream->pos, fit, debugstr_an(buffer,fit)); |
| memmove(pStream->buffer + pStream->pos, buffer, fit); |
| len -= fit; |
| buffer += fit; |
| pStream->pos += fit; |
| if (pStream->pos == STREAMOUT_BUFFER_SIZE) { |
| if (!ME_StreamOutFlush(pStream)) |
| return FALSE; |
| } |
| } |
| return TRUE; |
| } |
| |
| |
| static BOOL |
| ME_StreamOutPrint(ME_OutStream *pStream, const char *format, ...) |
| { |
| char string[STREAMOUT_BUFFER_SIZE]; /* This is going to be enough */ |
| int len; |
| va_list valist; |
| |
| va_start(valist, format); |
| len = vsnprintf(string, sizeof(string), format, valist); |
| va_end(valist); |
| |
| return ME_StreamOutMove(pStream, string, len); |
| } |
| |
| #define HEX_BYTES_PER_LINE 40 |
| |
| static BOOL |
| ME_StreamOutHexData(ME_OutStream *stream, const BYTE *data, UINT len) |
| { |
| |
| char line[HEX_BYTES_PER_LINE * 2 + 1]; |
| UINT size, i; |
| static const char hex[] = "0123456789abcdef"; |
| |
| while (len) |
| { |
| size = min( len, HEX_BYTES_PER_LINE ); |
| for (i = 0; i < size; i++) |
| { |
| line[i * 2] = hex[(*data >> 4) & 0xf]; |
| line[i * 2 + 1] = hex[*data & 0xf]; |
| data++; |
| } |
| line[size * 2] = '\n'; |
| if (!ME_StreamOutMove( stream, line, size * 2 + 1 )) |
| return FALSE; |
| len -= size; |
| } |
| return TRUE; |
| } |
| |
| static BOOL |
| ME_StreamOutRTFHeader(ME_OutStream *pStream, int dwFormat) |
| { |
| const char *cCharSet = NULL; |
| UINT nCodePage; |
| LANGID language; |
| BOOL success; |
| |
| if (dwFormat & SF_USECODEPAGE) { |
| CPINFOEXW info; |
| |
| switch (HIWORD(dwFormat)) { |
| case CP_ACP: |
| cCharSet = "ansi"; |
| nCodePage = GetACP(); |
| break; |
| case CP_OEMCP: |
| nCodePage = GetOEMCP(); |
| if (nCodePage == 437) |
| cCharSet = "pc"; |
| else if (nCodePage == 850) |
| cCharSet = "pca"; |
| else |
| cCharSet = "ansi"; |
| break; |
| case CP_UTF8: |
| nCodePage = CP_UTF8; |
| break; |
| default: |
| if (HIWORD(dwFormat) == CP_MACCP) { |
| cCharSet = "mac"; |
| nCodePage = 10000; /* MacRoman */ |
| } else { |
| cCharSet = "ansi"; |
| nCodePage = 1252; /* Latin-1 */ |
| } |
| if (GetCPInfoExW(HIWORD(dwFormat), 0, &info)) |
| nCodePage = info.CodePage; |
| } |
| } else { |
| cCharSet = "ansi"; |
| /* TODO: If the original document contained an \ansicpg value, retain it. |
| * Otherwise, M$ richedit emits a codepage number determined from the |
| * charset of the default font here. Anyway, this value is not used by |
| * the reader... */ |
| nCodePage = GetACP(); |
| } |
| if (nCodePage == CP_UTF8) |
| success = ME_StreamOutPrint(pStream, "{\\urtf"); |
| else |
| success = ME_StreamOutPrint(pStream, "{\\rtf1\\%s\\ansicpg%u\\uc1", cCharSet, nCodePage); |
| |
| if (!success) |
| return FALSE; |
| |
| pStream->nDefaultCodePage = nCodePage; |
| |
| /* FIXME: This should be a document property */ |
| /* TODO: handle SFF_PLAINRTF */ |
| language = GetUserDefaultLangID(); |
| if (!ME_StreamOutPrint(pStream, "\\deff0\\deflang%u\\deflangfe%u", language, language)) |
| return FALSE; |
| |
| /* FIXME: This should be a document property */ |
| pStream->nDefaultFont = 0; |
| |
| return TRUE; |
| } |
| |
| static void add_font_to_fonttbl( ME_OutStream *stream, ME_Style *style ) |
| { |
| ME_FontTableItem *table = stream->fonttbl; |
| CHARFORMAT2W *fmt = &style->fmt; |
| WCHAR *face = fmt->szFaceName; |
| BYTE charset = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET; |
| int i; |
| |
| if (fmt->dwMask & CFM_FACE) |
| { |
| for (i = 0; i < stream->nFontTblLen; i++) |
| if (table[i].bCharSet == charset |
| && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face))) |
| break; |
| |
| if (i == stream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE) |
| { |
| table[i].bCharSet = charset; |
| table[i].szFaceName = face; |
| stream->nFontTblLen++; |
| } |
| } |
| } |
| |
| static BOOL find_font_in_fonttbl( ME_OutStream *stream, CHARFORMAT2W *fmt, unsigned int *idx ) |
| { |
| WCHAR *facename; |
| int i; |
| |
| *idx = 0; |
| if (fmt->dwMask & CFM_FACE) |
| facename = fmt->szFaceName; |
| else |
| facename = stream->fonttbl[0].szFaceName; |
| for (i = 0; i < stream->nFontTblLen; i++) |
| { |
| if (facename == stream->fonttbl[i].szFaceName |
| || !lstrcmpW(facename, stream->fonttbl[i].szFaceName)) |
| if (!(fmt->dwMask & CFM_CHARSET) |
| || fmt->bCharSet == stream->fonttbl[i].bCharSet) |
| { |
| *idx = i; |
| break; |
| } |
| } |
| |
| return i < stream->nFontTblLen; |
| } |
| |
| static void add_color_to_colortbl( ME_OutStream *stream, COLORREF color ) |
| { |
| int i; |
| |
| for (i = 1; i < stream->nColorTblLen; i++) |
| if (stream->colortbl[i] == color) |
| break; |
| |
| if (i == stream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) |
| { |
| stream->colortbl[i] = color; |
| stream->nColorTblLen++; |
| } |
| } |
| |
| static BOOL find_color_in_colortbl( ME_OutStream *stream, COLORREF color, unsigned int *idx ) |
| { |
| int i; |
| |
| *idx = 0; |
| for (i = 1; i < stream->nColorTblLen; i++) |
| { |
| if (stream->colortbl[i] == color) |
| { |
| *idx = i; |
| break; |
| } |
| } |
| |
| return i < stream->nFontTblLen; |
| } |
| |
| static BOOL |
| ME_StreamOutRTFFontAndColorTbl(ME_OutStream *pStream, ME_DisplayItem *pFirstRun, |
| ME_DisplayItem *pLastRun) |
| { |
| ME_DisplayItem *item = pFirstRun; |
| ME_FontTableItem *table = pStream->fonttbl; |
| unsigned int i; |
| ME_DisplayItem *pCell = NULL; |
| ME_Paragraph *prev_para = NULL; |
| |
| do { |
| CHARFORMAT2W *fmt = &item->member.run.style->fmt; |
| |
| add_font_to_fonttbl( pStream, item->member.run.style ); |
| |
| if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) |
| add_color_to_colortbl( pStream, fmt->crTextColor ); |
| if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) |
| add_color_to_colortbl( pStream, fmt->crBackColor ); |
| |
| if (item->member.run.para != prev_para) |
| { |
| /* check for any para numbering text */ |
| if (item->member.run.para->fmt.wNumbering) |
| add_font_to_fonttbl( pStream, item->member.run.para->para_num.style ); |
| |
| if ((pCell = item->member.para.pCell)) |
| { |
| ME_Border* borders[4] = { &pCell->member.cell.border.top, |
| &pCell->member.cell.border.left, |
| &pCell->member.cell.border.bottom, |
| &pCell->member.cell.border.right }; |
| for (i = 0; i < 4; i++) |
| if (borders[i]->width > 0) |
| add_color_to_colortbl( pStream, borders[i]->colorRef ); |
| } |
| |
| prev_para = item->member.run.para; |
| } |
| |
| if (item == pLastRun) |
| break; |
| item = ME_FindItemFwd(item, diRun); |
| } while (item); |
| |
| if (!ME_StreamOutPrint(pStream, "{\\fonttbl")) |
| return FALSE; |
| |
| for (i = 0; i < pStream->nFontTblLen; i++) { |
| if (table[i].bCharSet != DEFAULT_CHARSET) { |
| if (!ME_StreamOutPrint(pStream, "{\\f%u\\fcharset%u ", i, table[i].bCharSet)) |
| return FALSE; |
| } else { |
| if (!ME_StreamOutPrint(pStream, "{\\f%u ", i)) |
| return FALSE; |
| } |
| if (!ME_StreamOutRTFText(pStream, table[i].szFaceName, -1)) |
| return FALSE; |
| if (!ME_StreamOutPrint(pStream, ";}")) |
| return FALSE; |
| } |
| if (!ME_StreamOutPrint(pStream, "}\r\n")) |
| return FALSE; |
| |
| /* It seems like Open Office ignores \deff0 tag at RTF-header. |
| As result it can't correctly parse text before first \fN tag, |
| so we can put \f0 immediately after font table. This forces |
| parser to use the same font, that \deff0 specifies. |
| It makes OOffice happy */ |
| if (!ME_StreamOutPrint(pStream, "\\f0")) |
| return FALSE; |
| |
| /* Output the color table */ |
| if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) return FALSE; /* first entry is auto-color */ |
| for (i = 1; i < pStream->nColorTblLen; i++) |
| { |
| if (!ME_StreamOutPrint(pStream, "\\red%u\\green%u\\blue%u;", pStream->colortbl[i] & 0xFF, |
| (pStream->colortbl[i] >> 8) & 0xFF, (pStream->colortbl[i] >> 16) & 0xFF)) |
| return FALSE; |
| } |
| if (!ME_StreamOutPrint(pStream, "}")) return FALSE; |
| |
| return TRUE; |
| } |
| |
| static BOOL |
| ME_StreamOutRTFTableProps(ME_TextEditor *editor, ME_OutStream *pStream, |
| ME_DisplayItem *para) |
| { |
| ME_DisplayItem *cell; |
| char props[STREAMOUT_BUFFER_SIZE] = ""; |
| int i; |
| const char sideChar[4] = {'t','l','b','r'}; |
| |
| if (!ME_StreamOutPrint(pStream, "\\trowd")) |
| return FALSE; |
| if (!editor->bEmulateVersion10) { /* v4.1 */ |
| PARAFORMAT2 *pFmt = &ME_GetTableRowEnd(para)->member.para.fmt; |
| para = ME_GetTableRowStart(para); |
| cell = para->member.para.next_para->member.para.pCell; |
| assert(cell); |
| if (pFmt->dxOffset) |
| sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset); |
| if (pFmt->dxStartIndent) |
| sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent); |
| do { |
| ME_Border* borders[4] = { &cell->member.cell.border.top, |
| &cell->member.cell.border.left, |
| &cell->member.cell.border.bottom, |
| &cell->member.cell.border.right }; |
| for (i = 0; i < 4; i++) |
| { |
| if (borders[i]->width) |
| { |
| unsigned int idx; |
| COLORREF crColor = borders[i]->colorRef; |
| sprintf(props + strlen(props), "\\clbrdr%c", sideChar[i]); |
| sprintf(props + strlen(props), "\\brdrs"); |
| sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width); |
| if (find_color_in_colortbl( pStream, crColor, &idx )) |
| sprintf(props + strlen(props), "\\brdrcf%u", idx); |
| } |
| } |
| sprintf(props + strlen(props), "\\cellx%d", cell->member.cell.nRightBoundary); |
| cell = cell->member.cell.next_cell; |
| } while (cell->member.cell.next_cell); |
| } else { /* v1.0 - 3.0 */ |
| const ME_Border* borders[4] = { ¶->member.para.border.top, |
| ¶->member.para.border.left, |
| ¶->member.para.border.bottom, |
| ¶->member.para.border.right }; |
| PARAFORMAT2 *pFmt = ¶->member.para.fmt; |
| |
| assert(!(para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL))); |
| if (pFmt->dxOffset) |
| sprintf(props + strlen(props), "\\trgaph%d", pFmt->dxOffset); |
| if (pFmt->dxStartIndent) |
| sprintf(props + strlen(props), "\\trleft%d", pFmt->dxStartIndent); |
| for (i = 0; i < 4; i++) |
| { |
| if (borders[i]->width) |
| { |
| unsigned int idx; |
| COLORREF crColor = borders[i]->colorRef; |
| sprintf(props + strlen(props), "\\trbrdr%c", sideChar[i]); |
| sprintf(props + strlen(props), "\\brdrs"); |
| sprintf(props + strlen(props), "\\brdrw%d", borders[i]->width); |
| if (find_color_in_colortbl( pStream, crColor, &idx )) |
| sprintf(props + strlen(props), "\\brdrcf%u", idx); |
| } |
| } |
| for (i = 0; i < pFmt->cTabCount; i++) |
| { |
| sprintf(props + strlen(props), "\\cellx%d", pFmt->rgxTabs[i] & 0x00FFFFFF); |
| } |
| } |
| if (!ME_StreamOutPrint(pStream, props)) |
| return FALSE; |
| props[0] = '\0'; |
| return TRUE; |
| } |
| |
| static BOOL stream_out_para_num( ME_OutStream *stream, ME_Paragraph *para, BOOL pn_dest ) |
| { |
| static const char fmt_label[] = "{\\*\\pn\\pnlvlbody\\pnf%u\\pnindent%d\\pnstart%d%s%s}"; |
| static const char fmt_bullet[] = "{\\*\\pn\\pnlvlblt\\pnf%u\\pnindent%d{\\pntxtb\\'b7}}"; |
| static const char dec[] = "\\pndec"; |
| static const char lcltr[] = "\\pnlcltr"; |
| static const char ucltr[] = "\\pnucltr"; |
| static const char lcrm[] = "\\pnlcrm"; |
| static const char ucrm[] = "\\pnucrm"; |
| static const char period[] = "{\\pntxta.}"; |
| static const char paren[] = "{\\pntxta)}"; |
| static const char parens[] = "{\\pntxtb(}{\\pntxta)}"; |
| const char *type, *style = ""; |
| unsigned int idx; |
| |
| find_font_in_fonttbl( stream, ¶->para_num.style->fmt, &idx ); |
| |
| if (!ME_StreamOutPrint( stream, "{\\pntext\\f%u ", idx )) return FALSE; |
| if (!ME_StreamOutRTFText( stream, para->para_num.text->szData, para->para_num.text->nLen )) |
| return FALSE; |
| if (!ME_StreamOutPrint( stream, "\\tab}" )) return FALSE; |
| |
| if (!pn_dest) return TRUE; |
| |
| if (para->fmt.wNumbering == PFN_BULLET) |
| { |
| if (!ME_StreamOutPrint( stream, fmt_bullet, idx, para->fmt.wNumberingTab )) |
| return FALSE; |
| } |
| else |
| { |
| switch (para->fmt.wNumbering) |
| { |
| case PFN_ARABIC: |
| default: |
| type = dec; |
| break; |
| case PFN_LCLETTER: |
| type = lcltr; |
| break; |
| case PFN_UCLETTER: |
| type = ucltr; |
| break; |
| case PFN_LCROMAN: |
| type = lcrm; |
| break; |
| case PFN_UCROMAN: |
| type = ucrm; |
| break; |
| } |
| switch (para->fmt.wNumberingStyle & 0xf00) |
| { |
| case PFNS_PERIOD: |
| style = period; |
| break; |
| case PFNS_PAREN: |
| style = paren; |
| break; |
| case PFNS_PARENS: |
| style = parens; |
| break; |
| } |
| |
| if (!ME_StreamOutPrint( stream, fmt_label, idx, para->fmt.wNumberingTab, |
| para->fmt.wNumberingStart, type, style )) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static BOOL |
| ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream, |
| ME_DisplayItem *para) |
| { |
| PARAFORMAT2 *fmt = ¶->member.para.fmt; |
| char props[STREAMOUT_BUFFER_SIZE] = ""; |
| int i; |
| ME_Paragraph *prev_para = NULL; |
| |
| if (para->member.para.prev_para->type == diParagraph) |
| prev_para = ¶->member.para.prev_para->member.para; |
| |
| if (!editor->bEmulateVersion10) { /* v4.1 */ |
| if (para->member.para.nFlags & MEPF_ROWSTART) { |
| pStream->nNestingLevel++; |
| if (pStream->nNestingLevel == 1) { |
| if (!ME_StreamOutRTFTableProps(editor, pStream, para)) |
| return FALSE; |
| } |
| return TRUE; |
| } else if (para->member.para.nFlags & MEPF_ROWEND) { |
| pStream->nNestingLevel--; |
| if (pStream->nNestingLevel >= 1) { |
| if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops")) |
| return FALSE; |
| if (!ME_StreamOutRTFTableProps(editor, pStream, para)) |
| return FALSE; |
| if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n")) |
| return FALSE; |
| } else { |
| if (!ME_StreamOutPrint(pStream, "\\row\r\n")) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| } else { /* v1.0 - 3.0 */ |
| if (para->member.para.fmt.dwMask & PFM_TABLE && |
| para->member.para.fmt.wEffects & PFE_TABLE) |
| { |
| if (!ME_StreamOutRTFTableProps(editor, pStream, para)) |
| return FALSE; |
| } |
| } |
| |
| if (prev_para && !memcmp( fmt, &prev_para->fmt, sizeof(*fmt) )) |
| { |
| if (fmt->wNumbering) |
| return stream_out_para_num( pStream, ¶->member.para, FALSE ); |
| return TRUE; |
| } |
| |
| if (!ME_StreamOutPrint(pStream, "\\pard")) |
| return FALSE; |
| |
| if (fmt->wNumbering) |
| if (!stream_out_para_num( pStream, ¶->member.para, TRUE )) return FALSE; |
| |
| if (!editor->bEmulateVersion10) { /* v4.1 */ |
| if (pStream->nNestingLevel > 0) |
| strcat(props, "\\intbl"); |
| if (pStream->nNestingLevel > 1) |
| sprintf(props + strlen(props), "\\itap%d", pStream->nNestingLevel); |
| } else { /* v1.0 - 3.0 */ |
| if (fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE) |
| strcat(props, "\\intbl"); |
| } |
| |
| /* TODO: PFM_BORDER. M$ does not emit any keywords for these properties, and |
| * when streaming border keywords in, PFM_BORDER is set, but wBorder field is |
| * set very different from the documentation. |
| * (Tested with RichEdit 5.50.25.0601) */ |
| |
| if (fmt->dwMask & PFM_ALIGNMENT) { |
| switch (fmt->wAlignment) { |
| case PFA_LEFT: |
| /* Default alignment: not emitted */ |
| break; |
| case PFA_RIGHT: |
| strcat(props, "\\qr"); |
| break; |
| case PFA_CENTER: |
| strcat(props, "\\qc"); |
| break; |
| case PFA_JUSTIFY: |
| strcat(props, "\\qj"); |
| break; |
| } |
| } |
| |
| if (fmt->dwMask & PFM_LINESPACING) { |
| /* FIXME: MSDN says that the bLineSpacingRule field is controlled by the |
| * PFM_SPACEAFTER flag. Is that true? I don't believe so. */ |
| switch (fmt->bLineSpacingRule) { |
| case 0: /* Single spacing */ |
| strcat(props, "\\sl-240\\slmult1"); |
| break; |
| case 1: /* 1.5 spacing */ |
| strcat(props, "\\sl-360\\slmult1"); |
| break; |
| case 2: /* Double spacing */ |
| strcat(props, "\\sl-480\\slmult1"); |
| break; |
| case 3: |
| sprintf(props + strlen(props), "\\sl%d\\slmult0", fmt->dyLineSpacing); |
| break; |
| case 4: |
| sprintf(props + strlen(props), "\\sl-%d\\slmult0", fmt->dyLineSpacing); |
| break; |
| case 5: |
| sprintf(props + strlen(props), "\\sl-%d\\slmult1", fmt->dyLineSpacing * 240 / 20); |
| break; |
| } |
| } |
| |
| if (fmt->dwMask & PFM_DONOTHYPHEN && fmt->wEffects & PFE_DONOTHYPHEN) |
| strcat(props, "\\hyph0"); |
| if (fmt->dwMask & PFM_KEEP && fmt->wEffects & PFE_KEEP) |
| strcat(props, "\\keep"); |
| if (fmt->dwMask & PFM_KEEPNEXT && fmt->wEffects & PFE_KEEPNEXT) |
| strcat(props, "\\keepn"); |
| if (fmt->dwMask & PFM_NOLINENUMBER && fmt->wEffects & PFE_NOLINENUMBER) |
| strcat(props, "\\noline"); |
| if (fmt->dwMask & PFM_NOWIDOWCONTROL && fmt->wEffects & PFE_NOWIDOWCONTROL) |
| strcat(props, "\\nowidctlpar"); |
| if (fmt->dwMask & PFM_PAGEBREAKBEFORE && fmt->wEffects & PFE_PAGEBREAKBEFORE) |
| strcat(props, "\\pagebb"); |
| if (fmt->dwMask & PFM_RTLPARA && fmt->wEffects & PFE_RTLPARA) |
| strcat(props, "\\rtlpar"); |
| if (fmt->dwMask & PFM_SIDEBYSIDE && fmt->wEffects & PFE_SIDEBYSIDE) |
| strcat(props, "\\sbys"); |
| |
| if (!(editor->bEmulateVersion10 && /* v1.0 - 3.0 */ |
| fmt->dwMask & PFM_TABLE && fmt->wEffects & PFE_TABLE)) |
| { |
| if (fmt->dxOffset) |
| sprintf(props + strlen(props), "\\li%d", fmt->dxOffset); |
| if (fmt->dxStartIndent) |
| sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent); |
| if (fmt->dxRightIndent) |
| sprintf(props + strlen(props), "\\ri%d", fmt->dxRightIndent); |
| if (fmt->dwMask & PFM_TABSTOPS) { |
| static const char * const leader[6] = { "", "\\tldot", "\\tlhyph", "\\tlul", "\\tlth", "\\tleq" }; |
| |
| for (i = 0; i < fmt->cTabCount; i++) { |
| switch ((fmt->rgxTabs[i] >> 24) & 0xF) { |
| case 1: |
| strcat(props, "\\tqc"); |
| break; |
| case 2: |
| strcat(props, "\\tqr"); |
| break; |
| case 3: |
| strcat(props, "\\tqdec"); |
| break; |
| case 4: |
| /* Word bar tab (vertical bar). Handled below */ |
| break; |
| } |
| if (fmt->rgxTabs[i] >> 28 <= 5) |
| strcat(props, leader[fmt->rgxTabs[i] >> 28]); |
| sprintf(props+strlen(props), "\\tx%d", fmt->rgxTabs[i]&0x00FFFFFF); |
| } |
| } |
| } |
| if (fmt->dySpaceAfter) |
| sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter); |
| if (fmt->dySpaceBefore) |
| sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore); |
| if (fmt->sStyle != -1) |
| sprintf(props + strlen(props), "\\s%d", fmt->sStyle); |
| |
| if (fmt->dwMask & PFM_SHADING) { |
| static const char * const style[16] = { "", "\\bgdkhoriz", "\\bgdkvert", "\\bgdkfdiag", |
| "\\bgdkbdiag", "\\bgdkcross", "\\bgdkdcross", |
| "\\bghoriz", "\\bgvert", "\\bgfdiag", |
| "\\bgbdiag", "\\bgcross", "\\bgdcross", |
| "", "", "" }; |
| if (fmt->wShadingWeight) |
| sprintf(props + strlen(props), "\\shading%d", fmt->wShadingWeight); |
| if (fmt->wShadingStyle & 0xF) |
| strcat(props, style[fmt->wShadingStyle & 0xF]); |
| sprintf(props + strlen(props), "\\cfpat%d\\cbpat%d", |
| (fmt->wShadingStyle >> 4) & 0xF, (fmt->wShadingStyle >> 8) & 0xF); |
| } |
| if (*props) |
| strcat(props, " "); |
| |
| if (*props && !ME_StreamOutPrint(pStream, props)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| |
| static BOOL |
| ME_StreamOutRTFCharProps(ME_OutStream *pStream, CHARFORMAT2W *fmt) |
| { |
| char props[STREAMOUT_BUFFER_SIZE] = ""; |
| unsigned int i; |
| CHARFORMAT2W *old_fmt = &pStream->cur_fmt; |
| static const struct |
| { |
| DWORD effect; |
| const char *on, *off; |
| } effects[] = |
| { |
| { CFE_ALLCAPS, "\\caps", "\\caps0" }, |
| { CFE_BOLD, "\\b", "\\b0" }, |
| { CFE_DISABLED, "\\disabled", "\\disabled0" }, |
| { CFE_EMBOSS, "\\embo", "\\embo0" }, |
| { CFE_HIDDEN, "\\v", "\\v0" }, |
| { CFE_IMPRINT, "\\impr", "\\impr0" }, |
| { CFE_ITALIC, "\\i", "\\i0" }, |
| { CFE_OUTLINE, "\\outl", "\\outl0" }, |
| { CFE_PROTECTED, "\\protect", "\\protect0" }, |
| { CFE_SHADOW, "\\shad", "\\shad0" }, |
| { CFE_SMALLCAPS, "\\scaps", "\\scaps0" }, |
| { CFE_STRIKEOUT, "\\strike", "\\strike0" }, |
| }; |
| |
| for (i = 0; i < sizeof(effects) / sizeof(effects[0]); i++) |
| { |
| if ((old_fmt->dwEffects ^ fmt->dwEffects) & effects[i].effect) |
| strcat( props, fmt->dwEffects & effects[i].effect ? effects[i].on : effects[i].off ); |
| } |
| |
| if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOBACKCOLOR || |
| old_fmt->crBackColor != fmt->crBackColor) |
| { |
| if (fmt->dwEffects & CFE_AUTOBACKCOLOR) i = 0; |
| else find_color_in_colortbl( pStream, fmt->crBackColor, &i ); |
| sprintf(props + strlen(props), "\\cb%u", i); |
| } |
| if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_AUTOCOLOR || |
| old_fmt->crTextColor != fmt->crTextColor) |
| { |
| if (fmt->dwEffects & CFE_AUTOCOLOR) i = 0; |
| else find_color_in_colortbl( pStream, fmt->crTextColor, &i ); |
| sprintf(props + strlen(props), "\\cf%u", i); |
| } |
| |
| if (old_fmt->bAnimation != fmt->bAnimation) |
| sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation); |
| if (old_fmt->wKerning != fmt->wKerning) |
| sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning); |
| |
| if (old_fmt->lcid != fmt->lcid) |
| { |
| /* TODO: handle SFF_PLAINRTF */ |
| if (LOWORD(fmt->lcid) == 1024) |
| strcat(props, "\\noproof\\lang1024\\langnp1024\\langfe1024\\langfenp1024"); |
| else |
| sprintf(props + strlen(props), "\\lang%u", LOWORD(fmt->lcid)); |
| } |
| |
| if (old_fmt->yOffset != fmt->yOffset) |
| { |
| if (fmt->yOffset >= 0) |
| sprintf(props + strlen(props), "\\up%d", fmt->yOffset); |
| else |
| sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset); |
| } |
| if (old_fmt->yHeight != fmt->yHeight) |
| sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10); |
| if (old_fmt->sSpacing != fmt->sSpacing) |
| sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing); |
| if ((old_fmt->dwEffects ^ fmt->dwEffects) & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) |
| { |
| if (fmt->dwEffects & CFE_SUBSCRIPT) |
| strcat(props, "\\sub"); |
| else if (fmt->dwEffects & CFE_SUPERSCRIPT) |
| strcat(props, "\\super"); |
| else |
| strcat(props, "\\nosupersub"); |
| } |
| if ((old_fmt->dwEffects ^ fmt->dwEffects) & CFE_UNDERLINE || |
| old_fmt->bUnderlineType != fmt->bUnderlineType) |
| { |
| BYTE type = (fmt->dwEffects & CFE_UNDERLINE) ? fmt->bUnderlineType : CFU_UNDERLINENONE; |
| switch (type) |
| { |
| case CFU_UNDERLINE: |
| strcat(props, "\\ul"); |
| break; |
| case CFU_UNDERLINEDOTTED: |
| strcat(props, "\\uld"); |
| break; |
| case CFU_UNDERLINEDOUBLE: |
| strcat(props, "\\uldb"); |
| break; |
| case CFU_UNDERLINEWORD: |
| strcat(props, "\\ulw"); |
| break; |
| case CFU_CF1UNDERLINE: |
| case CFU_UNDERLINENONE: |
| default: |
| strcat(props, "\\ulnone"); |
| break; |
| } |
| } |
| |
| if (strcmpW(old_fmt->szFaceName, fmt->szFaceName) || |
| old_fmt->bCharSet != fmt->bCharSet) |
| { |
| if (find_font_in_fonttbl( pStream, fmt, &i )) |
| { |
| sprintf(props + strlen(props), "\\f%u", i); |
| |
| /* In UTF-8 mode, charsets/codepages are not used */ |
| if (pStream->nDefaultCodePage != CP_UTF8) |
| { |
| if (pStream->fonttbl[i].bCharSet == DEFAULT_CHARSET) |
| pStream->nCodePage = pStream->nDefaultCodePage; |
| else |
| pStream->nCodePage = RTFCharSetToCodePage(NULL, pStream->fonttbl[i].bCharSet); |
| } |
| } |
| } |
| if (*props) |
| strcat(props, " "); |
| if (!ME_StreamOutPrint(pStream, props)) |
| return FALSE; |
| *old_fmt = *fmt; |
| return TRUE; |
| } |
| |
| |
| static BOOL |
| ME_StreamOutRTFText(ME_OutStream *pStream, const WCHAR *text, LONG nChars) |
| { |
| char buffer[STREAMOUT_BUFFER_SIZE]; |
| int pos = 0; |
| int fit, nBytes, i; |
| |
| if (nChars == -1) |
| nChars = lstrlenW(text); |
| |
| while (nChars) { |
| /* In UTF-8 mode, font charsets are not used. */ |
| if (pStream->nDefaultCodePage == CP_UTF8) { |
| /* 6 is the maximum character length in UTF-8 */ |
| fit = min(nChars, STREAMOUT_BUFFER_SIZE / 6); |
| nBytes = WideCharToMultiByte(CP_UTF8, 0, text, fit, buffer, |
| STREAMOUT_BUFFER_SIZE, NULL, NULL); |
| nChars -= fit; |
| text += fit; |
| for (i = 0; i < nBytes; i++) |
| if (buffer[i] == '{' || buffer[i] == '}' || buffer[i] == '\\') { |
| if (!ME_StreamOutPrint(pStream, "%.*s\\", i - pos, buffer + pos)) |
| return FALSE; |
| pos = i; |
| } |
| if (pos < nBytes) |
| if (!ME_StreamOutMove(pStream, buffer + pos, nBytes - pos)) |
| return FALSE; |
| pos = 0; |
| } else if (*text < 128) { |
| if (*text == '{' || *text == '}' || *text == '\\') |
| buffer[pos++] = '\\'; |
| buffer[pos++] = (char)(*text++); |
| nChars--; |
| } else { |
| BOOL unknown = FALSE; |
| char letter[3]; |
| |
| /* FIXME: In the MS docs for WideCharToMultiByte there is a big list of |
| * codepages including CP_SYMBOL for which the last parameter must be set |
| * to NULL for the function to succeed. But in Wine we need to care only |
| * about CP_SYMBOL */ |
| nBytes = WideCharToMultiByte(pStream->nCodePage, 0, text, 1, |
| letter, 3, NULL, |
| (pStream->nCodePage == CP_SYMBOL) ? NULL : &unknown); |
| if (unknown) |
| pos += sprintf(buffer + pos, "\\u%d?", (short)*text); |
| else if ((BYTE)*letter < 128) { |
| if (*letter == '{' || *letter == '}' || *letter == '\\') |
| buffer[pos++] = '\\'; |
| buffer[pos++] = *letter; |
| } else { |
| for (i = 0; i < nBytes; i++) |
| pos += sprintf(buffer + pos, "\\'%02x", (BYTE)letter[i]); |
| } |
| text++; |
| nChars--; |
| } |
| if (pos >= STREAMOUT_BUFFER_SIZE - 11) { |
| if (!ME_StreamOutMove(pStream, buffer, pos)) |
| return FALSE; |
| pos = 0; |
| } |
| } |
| return ME_StreamOutMove(pStream, buffer, pos); |
| } |
| |
| static BOOL stream_out_graphics( ME_TextEditor *editor, ME_OutStream *stream, |
| ME_Run *run ) |
| { |
| IDataObject *data; |
| HRESULT hr; |
| FORMATETC fmt = { CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }; |
| STGMEDIUM med = { TYMED_NULL }; |
| BOOL ret = FALSE; |
| ENHMETAHEADER *emf_bits = NULL; |
| UINT size; |
| SIZE goal, pic; |
| ME_Context c; |
| |
| hr = IOleObject_QueryInterface( run->ole_obj->poleobj, &IID_IDataObject, (void **)&data ); |
| if (FAILED(hr)) return FALSE; |
| |
| ME_InitContext( &c, editor, ITextHost_TxGetDC( editor->texthost ) ); |
| hr = IDataObject_QueryGetData( data, &fmt ); |
| if (hr != S_OK) goto done; |
| |
| hr = IDataObject_GetData( data, &fmt, &med ); |
| if (FAILED(hr)) goto done; |
| if (med.tymed != TYMED_ENHMF) goto done; |
| |
| size = GetEnhMetaFileBits( med.u.hEnhMetaFile, 0, NULL ); |
| if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done; |
| |
| emf_bits = HeapAlloc( GetProcessHeap(), 0, size ); |
| if (!emf_bits) goto done; |
| |
| size = GetEnhMetaFileBits( med.u.hEnhMetaFile, size, (BYTE *)emf_bits ); |
| if (size < FIELD_OFFSET(ENHMETAHEADER, cbPixelFormat)) goto done; |
| |
| /* size_in_pixels = (frame_size / 100) * szlDevice / szlMillimeters |
| pic = size_in_pixels * 2540 / dpi */ |
| pic.cx = MulDiv( emf_bits->rclFrame.right - emf_bits->rclFrame.left, emf_bits->szlDevice.cx * 254, |
| emf_bits->szlMillimeters.cx * c.dpi.cx * 10 ); |
| pic.cy = MulDiv( emf_bits->rclFrame.bottom - emf_bits->rclFrame.top, emf_bits->szlDevice.cy * 254, |
| emf_bits->szlMillimeters.cy * c.dpi.cy * 10 ); |
| |
| /* convert goal size to twips */ |
| goal.cx = MulDiv( run->ole_obj->sizel.cx, 144, 254 ); |
| goal.cy = MulDiv( run->ole_obj->sizel.cy, 144, 254 ); |
| |
| if (!ME_StreamOutPrint( stream, "{\\*\\shppict{\\pict\\emfblip\\picw%d\\pich%d\\picwgoal%d\\pichgoal%d\n", |
| pic.cx, pic.cy, goal.cx, goal.cy )) |
| goto done; |
| |
| if (!ME_StreamOutHexData( stream, (BYTE *)emf_bits, size )) |
| goto done; |
| |
| if (!ME_StreamOutPrint( stream, "}}\n" )) |
| goto done; |
| |
| ret = TRUE; |
| |
| done: |
| ME_DestroyContext( &c ); |
| HeapFree( GetProcessHeap(), 0, emf_bits ); |
| ReleaseStgMedium( &med ); |
| IDataObject_Release( data ); |
| return ret; |
| } |
| |
| static BOOL ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream, |
| const ME_Cursor *start, int nChars, int dwFormat) |
| { |
| ME_Cursor cursor = *start; |
| ME_DisplayItem *prev_para = NULL; |
| ME_Cursor endCur = cursor; |
| |
| ME_MoveCursorChars(editor, &endCur, nChars, TRUE); |
| |
| if (!ME_StreamOutRTFHeader(pStream, dwFormat)) |
| return FALSE; |
| |
| if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun)) |
| return FALSE; |
| |
| /* TODO: stylesheet table */ |
| |
| if (!ME_StreamOutPrint(pStream, "{\\*\\generator Wine Riched20 2.0;}")) |
| return FALSE; |
| |
| /* TODO: information group */ |
| |
| /* TODO: document formatting properties */ |
| |
| /* FIXME: We have only one document section */ |
| |
| /* TODO: section formatting properties */ |
| |
| do { |
| if (cursor.pPara != prev_para) |
| { |
| prev_para = cursor.pPara; |
| if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara)) |
| return FALSE; |
| } |
| |
| if (cursor.pRun == endCur.pRun && !endCur.nOffset) |
| break; |
| TRACE("flags %xh\n", cursor.pRun->member.run.nFlags); |
| /* TODO: emit embedded objects */ |
| if (cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND)) |
| continue; |
| if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) { |
| if (!stream_out_graphics(editor, pStream, &cursor.pRun->member.run)) |
| return FALSE; |
| } else if (cursor.pRun->member.run.nFlags & MERF_TAB) { |
| if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */ |
| cursor.pPara->member.para.fmt.dwMask & PFM_TABLE && |
| cursor.pPara->member.para.fmt.wEffects & PFE_TABLE) |
| { |
| if (!ME_StreamOutPrint(pStream, "\\cell ")) |
| return FALSE; |
| } else { |
| if (!ME_StreamOutPrint(pStream, "\\tab ")) |
| return FALSE; |
| } |
| } else if (cursor.pRun->member.run.nFlags & MERF_ENDCELL) { |
| if (pStream->nNestingLevel > 1) { |
| if (!ME_StreamOutPrint(pStream, "\\nestcell ")) |
| return FALSE; |
| } else { |
| if (!ME_StreamOutPrint(pStream, "\\cell ")) |
| return FALSE; |
| } |
| nChars--; |
| } else if (cursor.pRun->member.run.nFlags & MERF_ENDPARA) { |
| if (cursor.pPara->member.para.fmt.dwMask & PFM_TABLE && |
| cursor.pPara->member.para.fmt.wEffects & PFE_TABLE && |
| !(cursor.pPara->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND|MEPF_CELL))) |
| { |
| if (!ME_StreamOutPrint(pStream, "\\row\r\n")) |
| return FALSE; |
| } else { |
| if (!ME_StreamOutPrint(pStream, "\\par\r\n")) |
| return FALSE; |
| } |
| /* Skip as many characters as required by current line break */ |
| nChars = max(0, nChars - cursor.pRun->member.run.len); |
| } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) { |
| if (!ME_StreamOutPrint(pStream, "\\line\r\n")) |
| return FALSE; |
| nChars--; |
| } else { |
| int nEnd; |
| |
| TRACE("style %p\n", cursor.pRun->member.run.style); |
| if (!ME_StreamOutRTFCharProps(pStream, &cursor.pRun->member.run.style->fmt)) |
| return FALSE; |
| |
| nEnd = (cursor.pRun == endCur.pRun) ? endCur.nOffset : cursor.pRun->member.run.len; |
| if (!ME_StreamOutRTFText(pStream, get_text( &cursor.pRun->member.run, cursor.nOffset ), |
| nEnd - cursor.nOffset)) |
| return FALSE; |
| cursor.nOffset = 0; |
| } |
| } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE)); |
| |
| if (!ME_StreamOutMove(pStream, "}\0", 2)) |
| return FALSE; |
| return TRUE; |
| } |
| |
| |
| static BOOL ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream, |
| const ME_Cursor *start, int nChars, DWORD dwFormat) |
| { |
| ME_Cursor cursor = *start; |
| int nLen; |
| UINT nCodePage = CP_ACP; |
| char *buffer = NULL; |
| int nBufLen = 0; |
| BOOL success = TRUE; |
| |
| if (!cursor.pRun) |
| return FALSE; |
| |
| if (dwFormat & SF_USECODEPAGE) |
| nCodePage = HIWORD(dwFormat); |
| |
| /* TODO: Handle SF_TEXTIZED */ |
| |
| while (success && nChars && cursor.pRun) { |
| nLen = min(nChars, cursor.pRun->member.run.len - cursor.nOffset); |
| |
| if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA) |
| { |
| static const WCHAR szEOL[] = { '\r', '\n' }; |
| |
| /* richedit 2.0 - all line breaks are \r\n */ |
| if (dwFormat & SF_UNICODE) |
| success = ME_StreamOutMove(pStream, (const char *)szEOL, sizeof(szEOL)); |
| else |
| success = ME_StreamOutMove(pStream, "\r\n", 2); |
| } else { |
| if (dwFormat & SF_UNICODE) |
| success = ME_StreamOutMove(pStream, (const char *)(get_text( &cursor.pRun->member.run, cursor.nOffset )), |
| sizeof(WCHAR) * nLen); |
| else { |
| int nSize; |
| |
| nSize = WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ), |
| nLen, NULL, 0, NULL, NULL); |
| if (nSize > nBufLen) { |
| FREE_OBJ(buffer); |
| buffer = ALLOC_N_OBJ(char, nSize); |
| nBufLen = nSize; |
| } |
| WideCharToMultiByte(nCodePage, 0, get_text( &cursor.pRun->member.run, cursor.nOffset ), |
| nLen, buffer, nSize, NULL, NULL); |
| success = ME_StreamOutMove(pStream, buffer, nSize); |
| } |
| } |
| |
| nChars -= nLen; |
| cursor.nOffset = 0; |
| cursor.pRun = ME_FindItemFwd(cursor.pRun, diRun); |
| } |
| |
| FREE_OBJ(buffer); |
| return success; |
| } |
| |
| |
| LRESULT ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat, |
| const ME_Cursor *start, |
| int nChars, EDITSTREAM *stream) |
| { |
| ME_OutStream *pStream = ME_StreamOutInit(editor, stream); |
| |
| if (dwFormat & SF_RTF) |
| ME_StreamOutRTF(editor, pStream, start, nChars, dwFormat); |
| else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED) |
| ME_StreamOutText(editor, pStream, start, nChars, dwFormat); |
| if (!pStream->stream->dwError) |
| ME_StreamOutFlush(pStream); |
| return ME_StreamOutFree(pStream); |
| } |
| |
| LRESULT |
| ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream) |
| { |
| ME_Cursor start; |
| int nChars; |
| |
| if (dwFormat & SFF_SELECTION) { |
| int nStart, nTo; |
| start = editor->pCursors[ME_GetSelectionOfs(editor, &nStart, &nTo)]; |
| nChars = nTo - nStart; |
| } else { |
| ME_SetCursorToStart(editor, &start); |
| nChars = ME_GetTextLength(editor); |
| /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */ |
| if (dwFormat & SF_RTF) |
| nChars++; |
| } |
| return ME_StreamOutRange(editor, dwFormat, &start, nChars, stream); |
| } |