|  | /* | 
|  | * 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" | 
|  |  | 
|  | #include "editor.h" | 
|  | #include "rtf.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(richedit); | 
|  |  | 
|  |  | 
|  | 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; | 
|  | return pStream; | 
|  | } | 
|  |  | 
|  |  | 
|  | static BOOL | 
|  | ME_StreamOutFlush(ME_OutStream *pStream) | 
|  | { | 
|  | LONG nStart = 0; | 
|  | LONG nWritten = 0; | 
|  | LONG nRemaining = 0; | 
|  | EDITSTREAM *stream = pStream->stream; | 
|  |  | 
|  | do { | 
|  | TRACE("sending %u bytes\n", pStream->pos - nStart); | 
|  | /* Some apps seem not to set *pcb unless a problem arises, relying | 
|  | on initial random nWritten value, which is usually >STREAMOUT_BUFFER_SIZE */ | 
|  | nRemaining = pStream->pos - nStart; | 
|  | nWritten = 0xDEADBEEF; | 
|  | stream->dwError = stream->pfnCallback(stream->dwCookie, (LPBYTE)pStream->buffer + nStart, | 
|  | pStream->pos - nStart, &nWritten); | 
|  | TRACE("error=%u written=%u\n", stream->dwError, nWritten); | 
|  | if (nWritten > (pStream->pos - nStart) || nWritten<0) { | 
|  | FIXME("Invalid returned written size *pcb: 0x%x (%d) instead of %d\n", | 
|  | (unsigned)nWritten, nWritten, nRemaining); | 
|  | nWritten = nRemaining; | 
|  | } | 
|  | if (nWritten == 0 || stream->dwError) | 
|  | return FALSE; | 
|  | pStream->written += nWritten; | 
|  | nStart += nWritten; | 
|  | } while (nStart < pStream->pos); | 
|  | 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); | 
|  | } | 
|  |  | 
|  |  | 
|  | 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 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 *pLastPara = ME_GetParagraph(pLastRun); | 
|  | ME_DisplayItem *pCell = NULL; | 
|  |  | 
|  | do { | 
|  | CHARFORMAT2W *fmt = &item->member.run.style->fmt; | 
|  | COLORREF crColor; | 
|  |  | 
|  | if (fmt->dwMask & CFM_FACE) { | 
|  | WCHAR *face = fmt->szFaceName; | 
|  | BYTE bCharSet = (fmt->dwMask & CFM_CHARSET) ? fmt->bCharSet : DEFAULT_CHARSET; | 
|  |  | 
|  | for (i = 0; i < pStream->nFontTblLen; i++) | 
|  | if (table[i].bCharSet == bCharSet | 
|  | && (table[i].szFaceName == face || !lstrcmpW(table[i].szFaceName, face))) | 
|  | break; | 
|  | if (i == pStream->nFontTblLen && i < STREAMOUT_FONTTBL_SIZE) { | 
|  | table[i].bCharSet = bCharSet; | 
|  | table[i].szFaceName = face; | 
|  | pStream->nFontTblLen++; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (fmt->dwMask & CFM_COLOR && !(fmt->dwEffects & CFE_AUTOCOLOR)) { | 
|  | crColor = fmt->crTextColor; | 
|  | for (i = 1; i < pStream->nColorTblLen; i++) | 
|  | if (pStream->colortbl[i] == crColor) | 
|  | break; | 
|  | if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) { | 
|  | pStream->colortbl[i] = crColor; | 
|  | pStream->nColorTblLen++; | 
|  | } | 
|  | } | 
|  | if (fmt->dwMask & CFM_BACKCOLOR && !(fmt->dwEffects & CFE_AUTOBACKCOLOR)) { | 
|  | crColor = fmt->crBackColor; | 
|  | for (i = 1; i < pStream->nColorTblLen; i++) | 
|  | if (pStream->colortbl[i] == crColor) | 
|  | break; | 
|  | if (i == pStream->nColorTblLen && i < STREAMOUT_COLORTBL_SIZE) { | 
|  | pStream->colortbl[i] = crColor; | 
|  | pStream->nColorTblLen++; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (item == pLastRun) | 
|  | break; | 
|  | item = ME_FindItemFwd(item, diRun); | 
|  | } while (item); | 
|  | item = ME_GetParagraph(pFirstRun); | 
|  | do { | 
|  | if (item->member.para.pCell && item->member.para.pCell) | 
|  | { | 
|  | pCell = item->member.para.pCell; | 
|  | if (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) | 
|  | { | 
|  | unsigned int j; | 
|  | COLORREF crColor = borders[i]->colorRef; | 
|  | for (j = 1; j < pStream->nColorTblLen; j++) | 
|  | if (pStream->colortbl[j] == crColor) | 
|  | break; | 
|  | if (j == pStream->nColorTblLen && j < STREAMOUT_COLORTBL_SIZE) { | 
|  | pStream->colortbl[j] = crColor; | 
|  | pStream->nColorTblLen++; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (item == pLastPara) | 
|  | break; | 
|  | item = item->member.para.next_para; | 
|  | } 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; | 
|  |  | 
|  | /* Output colors table if not empty */ | 
|  | if (pStream->nColorTblLen > 1) { | 
|  | if (!ME_StreamOutPrint(pStream, "{\\colortbl;")) | 
|  | return FALSE; | 
|  | 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.pFmt; | 
|  | 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 j; | 
|  | 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); | 
|  | for (j = 1; j < pStream->nColorTblLen; j++) { | 
|  | if (pStream->colortbl[j] == crColor) { | 
|  | sprintf(props + strlen(props), "\\brdrcf%u", j); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | 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 = para->member.para.pFmt; | 
|  |  | 
|  | 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 j; | 
|  | 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); | 
|  | for (j = 1; j < pStream->nColorTblLen; j++) { | 
|  | if (pStream->colortbl[j] == crColor) { | 
|  | sprintf(props + strlen(props), "\\brdrcf%u", j); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | 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 | 
|  | ME_StreamOutRTFParaProps(ME_TextEditor *editor, ME_OutStream *pStream, | 
|  | const ME_DisplayItem *para) | 
|  | { | 
|  | PARAFORMAT2 *fmt = para->member.para.pFmt; | 
|  | char props[STREAMOUT_BUFFER_SIZE] = ""; | 
|  | int i; | 
|  |  | 
|  | /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */ | 
|  | if (!ME_StreamOutPrint(pStream, "\\pard")) | 
|  | 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->dwMask & PFM_OFFSET) | 
|  | sprintf(props + strlen(props), "\\li%d", fmt->dxOffset); | 
|  | if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT) | 
|  | sprintf(props + strlen(props), "\\fi%d", fmt->dxStartIndent); | 
|  | if (fmt->dwMask & PFM_RIGHTINDENT) | 
|  | 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->dwMask & PFM_SPACEAFTER) | 
|  | sprintf(props + strlen(props), "\\sa%d", fmt->dySpaceAfter); | 
|  | if (fmt->dwMask & PFM_SPACEBEFORE) | 
|  | sprintf(props + strlen(props), "\\sb%d", fmt->dySpaceBefore); | 
|  | if (fmt->dwMask & PFM_STYLE) | 
|  | 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 && !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; | 
|  |  | 
|  | if (fmt->dwMask & CFM_ALLCAPS && fmt->dwEffects & CFE_ALLCAPS) | 
|  | strcat(props, "\\caps"); | 
|  | if (fmt->dwMask & CFM_ANIMATION) | 
|  | sprintf(props + strlen(props), "\\animtext%u", fmt->bAnimation); | 
|  | if (fmt->dwMask & CFM_BACKCOLOR) { | 
|  | if (!(fmt->dwEffects & CFE_AUTOBACKCOLOR)) { | 
|  | for (i = 1; i < pStream->nColorTblLen; i++) | 
|  | if (pStream->colortbl[i] == fmt->crBackColor) { | 
|  | sprintf(props + strlen(props), "\\cb%u", i); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (fmt->dwMask & CFM_BOLD && fmt->dwEffects & CFE_BOLD) | 
|  | strcat(props, "\\b"); | 
|  | if (fmt->dwMask & CFM_COLOR) { | 
|  | if (!(fmt->dwEffects & CFE_AUTOCOLOR)) { | 
|  | for (i = 1; i < pStream->nColorTblLen; i++) | 
|  | if (pStream->colortbl[i] == fmt->crTextColor) { | 
|  | sprintf(props + strlen(props), "\\cf%u", i); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | /* TODO: CFM_DISABLED */ | 
|  | if (fmt->dwMask & CFM_EMBOSS && fmt->dwEffects & CFE_EMBOSS) | 
|  | strcat(props, "\\embo"); | 
|  | if (fmt->dwMask & CFM_HIDDEN && fmt->dwEffects & CFE_HIDDEN) | 
|  | strcat(props, "\\v"); | 
|  | if (fmt->dwMask & CFM_IMPRINT && fmt->dwEffects & CFE_IMPRINT) | 
|  | strcat(props, "\\impr"); | 
|  | if (fmt->dwMask & CFM_ITALIC && fmt->dwEffects & CFE_ITALIC) | 
|  | strcat(props, "\\i"); | 
|  | if (fmt->dwMask & CFM_KERNING) | 
|  | sprintf(props + strlen(props), "\\kerning%u", fmt->wKerning); | 
|  | if (fmt->dwMask & CFM_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)); | 
|  | } | 
|  | /* CFM_LINK is not streamed out by M$ */ | 
|  | if (fmt->dwMask & CFM_OFFSET) { | 
|  | if (fmt->yOffset >= 0) | 
|  | sprintf(props + strlen(props), "\\up%d", fmt->yOffset); | 
|  | else | 
|  | sprintf(props + strlen(props), "\\dn%d", -fmt->yOffset); | 
|  | } | 
|  | if (fmt->dwMask & CFM_OUTLINE && fmt->dwEffects & CFE_OUTLINE) | 
|  | strcat(props, "\\outl"); | 
|  | if (fmt->dwMask & CFM_PROTECTED && fmt->dwEffects & CFE_PROTECTED) | 
|  | strcat(props, "\\protect"); | 
|  | /* TODO: CFM_REVISED CFM_REVAUTHOR - probably using rsidtbl? */ | 
|  | if (fmt->dwMask & CFM_SHADOW && fmt->dwEffects & CFE_SHADOW) | 
|  | strcat(props, "\\shad"); | 
|  | if (fmt->dwMask & CFM_SIZE) | 
|  | sprintf(props + strlen(props), "\\fs%d", fmt->yHeight / 10); | 
|  | if (fmt->dwMask & CFM_SMALLCAPS && fmt->dwEffects & CFE_SMALLCAPS) | 
|  | strcat(props, "\\scaps"); | 
|  | if (fmt->dwMask & CFM_SPACING) | 
|  | sprintf(props + strlen(props), "\\expnd%u\\expndtw%u", fmt->sSpacing / 5, fmt->sSpacing); | 
|  | if (fmt->dwMask & CFM_STRIKEOUT && fmt->dwEffects & CFE_STRIKEOUT) | 
|  | strcat(props, "\\strike"); | 
|  | if (fmt->dwMask & CFM_STYLE) { | 
|  | sprintf(props + strlen(props), "\\cs%u", fmt->sStyle); | 
|  | /* TODO: emit style contents here */ | 
|  | } | 
|  | if (fmt->dwMask & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT)) { | 
|  | if (fmt->dwEffects & CFE_SUBSCRIPT) | 
|  | strcat(props, "\\sub"); | 
|  | else if (fmt->dwEffects & CFE_SUPERSCRIPT) | 
|  | strcat(props, "\\super"); | 
|  | } | 
|  | if (fmt->dwMask & CFM_UNDERLINE || fmt->dwMask & CFM_UNDERLINETYPE) { | 
|  | if (fmt->dwMask & CFM_UNDERLINETYPE) | 
|  | switch (fmt->bUnderlineType) { | 
|  | case CFU_CF1UNDERLINE: | 
|  | 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_UNDERLINENONE: | 
|  | default: | 
|  | strcat(props, "\\ul0"); | 
|  | break; | 
|  | } | 
|  | else if (fmt->dwEffects & CFE_UNDERLINE) | 
|  | strcat(props, "\\ul"); | 
|  | } | 
|  | /* FIXME: How to emit CFM_WEIGHT? */ | 
|  |  | 
|  | if (fmt->dwMask & CFM_FACE || fmt->dwMask & CFM_CHARSET) { | 
|  | WCHAR *szFaceName; | 
|  |  | 
|  | if (fmt->dwMask & CFM_FACE) | 
|  | szFaceName = fmt->szFaceName; | 
|  | else | 
|  | szFaceName = pStream->fonttbl[0].szFaceName; | 
|  | for (i = 0; i < pStream->nFontTblLen; i++) { | 
|  | if (szFaceName == pStream->fonttbl[i].szFaceName | 
|  | || !lstrcmpW(szFaceName, pStream->fonttbl[i].szFaceName)) | 
|  | if (!(fmt->dwMask & CFM_CHARSET) | 
|  | || fmt->bCharSet == pStream->fonttbl[i].bCharSet) | 
|  | break; | 
|  | } | 
|  | if (i < pStream->nFontTblLen) | 
|  | { | 
|  | if (i != pStream->nDefaultFont) | 
|  | 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; | 
|  | 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 ME_StreamOutRTF(ME_TextEditor *editor, ME_OutStream *pStream, | 
|  | const ME_Cursor *start, int nChars, int dwFormat) | 
|  | { | 
|  | ME_Cursor cursor = *start; | 
|  | ME_DisplayItem *prev_para = cursor.pPara; | 
|  | ME_Cursor endCur = cursor; | 
|  |  | 
|  | ME_MoveCursorChars(editor, &endCur, nChars); | 
|  |  | 
|  | if (!ME_StreamOutRTFHeader(pStream, dwFormat)) | 
|  | return FALSE; | 
|  |  | 
|  | if (!ME_StreamOutRTFFontAndColorTbl(pStream, cursor.pRun, endCur.pRun)) | 
|  | return FALSE; | 
|  |  | 
|  | /* TODO: stylesheet table */ | 
|  |  | 
|  | /* FIXME: maybe emit something smarter for the generator? */ | 
|  | 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 */ | 
|  |  | 
|  | if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara)) | 
|  | return FALSE; | 
|  |  | 
|  | do { | 
|  | if (cursor.pPara != prev_para) | 
|  | { | 
|  | prev_para = cursor.pPara; | 
|  | if (!editor->bEmulateVersion10) { /* v4.1 */ | 
|  | if (cursor.pPara->member.para.nFlags & MEPF_ROWSTART) { | 
|  | pStream->nNestingLevel++; | 
|  | if (pStream->nNestingLevel == 1) { | 
|  | if (!ME_StreamOutRTFTableProps(editor, pStream, cursor.pPara)) | 
|  | return FALSE; | 
|  | } | 
|  | } else if (cursor.pPara->member.para.nFlags & MEPF_ROWEND) { | 
|  | pStream->nNestingLevel--; | 
|  | if (pStream->nNestingLevel >= 1) { | 
|  | if (!ME_StreamOutPrint(pStream, "{\\*\\nesttableprops")) | 
|  | return FALSE; | 
|  | if (!ME_StreamOutRTFTableProps(editor, pStream, cursor.pPara)) | 
|  | return FALSE; | 
|  | if (!ME_StreamOutPrint(pStream, "\\nestrow}{\\nonesttables\\par}\r\n")) | 
|  | return FALSE; | 
|  | } else { | 
|  | if (!ME_StreamOutPrint(pStream, "\\row \r\n")) | 
|  | return FALSE; | 
|  | } | 
|  | } else if (!ME_StreamOutRTFParaProps(editor, pStream, cursor.pPara)) { | 
|  | return FALSE; | 
|  | } | 
|  | } else { /* v1.0 - 3.0 */ | 
|  | if (cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE && | 
|  | cursor.pPara->member.para.pFmt->wEffects & PFE_TABLE) | 
|  | { | 
|  | if (!ME_StreamOutRTFTableProps(editor, pStream, cursor.pPara)) | 
|  | return FALSE; | 
|  | } | 
|  | 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)) | 
|  | break; | 
|  | if (cursor.pRun->member.run.nFlags & MERF_GRAPHICS) { | 
|  | FIXME("embedded objects are not handled\n"); | 
|  | } else if (cursor.pRun->member.run.nFlags & MERF_TAB) { | 
|  | if (editor->bEmulateVersion10 && /* v1.0 - 3.0 */ | 
|  | cursor.pPara->member.para.pFmt->dwMask & PFM_TABLE && | 
|  | cursor.pPara->member.para.pFmt->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.pFmt->dwMask & PFM_TABLE && | 
|  | cursor.pPara->member.para.pFmt->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, "\r\n\\par")) | 
|  | return FALSE; | 
|  | } | 
|  | /* Skip as many characters as required by current line break */ | 
|  | nChars = max(0, nChars - cursor.pRun->member.run.strText->nLen); | 
|  | } else if (cursor.pRun->member.run.nFlags & MERF_ENDROW) { | 
|  | if (!ME_StreamOutPrint(pStream, "\\line \r\n")) | 
|  | return FALSE; | 
|  | nChars--; | 
|  | } else { | 
|  | int nEnd; | 
|  |  | 
|  | if (!ME_StreamOutPrint(pStream, "{")) | 
|  | return FALSE; | 
|  | 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.strText->nLen; | 
|  | if (!ME_StreamOutRTFText(pStream, cursor.pRun->member.run.strText->szData + cursor.nOffset, | 
|  | nEnd - cursor.nOffset)) | 
|  | return FALSE; | 
|  | cursor.nOffset = 0; | 
|  | if (!ME_StreamOutPrint(pStream, "}")) | 
|  | return FALSE; | 
|  | } | 
|  | } while (cursor.pRun != endCur.pRun && ME_NextRun(&cursor.pPara, &cursor.pRun)); | 
|  |  | 
|  | 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.strText->nLen - cursor.nOffset); | 
|  |  | 
|  | if (!editor->bEmulateVersion10 && cursor.pRun->member.run.nFlags & MERF_ENDPARA) | 
|  | { | 
|  | static const WCHAR szEOL[2] = { '\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 *)(cursor.pRun->member.run.strText->szData + cursor.nOffset), | 
|  | sizeof(WCHAR) * nLen); | 
|  | else { | 
|  | int nSize; | 
|  |  | 
|  | nSize = WideCharToMultiByte(nCodePage, 0, cursor.pRun->member.run.strText->szData + cursor.nOffset, | 
|  | nLen, NULL, 0, NULL, NULL); | 
|  | if (nSize > nBufLen) { | 
|  | FREE_OBJ(buffer); | 
|  | buffer = ALLOC_N_OBJ(char, nSize); | 
|  | nBufLen = nSize; | 
|  | } | 
|  | WideCharToMultiByte(nCodePage, 0, cursor.pRun->member.run.strText->szData + 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); | 
|  | } |