| /* | |
| * 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 "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; | |
| 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, const ME_DisplayItem *pLastRun) | |
| { | |
| ME_DisplayItem *item = pFirstRun; | |
| ME_FontTableItem *table = pStream->fonttbl; | |
| int i; | |
| 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) { | |
| 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) { | |
| 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) { | |
| pStream->colortbl[i] = crColor; | |
| pStream->nColorTblLen++; | |
| } | |
| } | |
| 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, ";}\r\n")) | |
| return FALSE; | |
| } | |
| if (!ME_StreamOutPrint(pStream, "}")) | |
| 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_StreamOutRTFParaProps(ME_OutStream *pStream, const ME_DisplayItem *para) | |
| { | |
| PARAFORMAT2 *fmt = para->member.para.pFmt; | |
| char props[STREAMOUT_BUFFER_SIZE] = ""; | |
| int i; | |
| if (para->member.para.pCells) | |
| { | |
| ME_TableCell *cell = para->member.para.pCells; | |
| if (!ME_StreamOutPrint(pStream, "\\trowd")) | |
| return FALSE; | |
| do { | |
| sprintf(props, "\\cellx%d", cell->nRightBoundary); | |
| if (!ME_StreamOutPrint(pStream, props)) | |
| return FALSE; | |
| cell = cell->next; | |
| } while (cell); | |
| props[0] = '\0'; | |
| } | |
| /* TODO: Don't emit anything if the last PARAFORMAT2 is inherited */ | |
| if (!ME_StreamOutPrint(pStream, "\\pard")) | |
| return FALSE; | |
| if (para->member.para.bTable) | |
| 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 (fmt->dwMask & PFM_TABLE && fmt->dwMask & PFE_TABLE) | |
| strcat(props, "\\intbl"); | |
| 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_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_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_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] = ""; | |
| 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, int nStart, int nChars, int dwFormat) | |
| { | |
| ME_DisplayItem *p, *pEnd, *pPara; | |
| int nOffset, nEndLen; | |
| ME_RunOfsFromCharOfs(editor, nStart, &p, &nOffset); | |
| ME_RunOfsFromCharOfs(editor, nStart+nChars, &pEnd, &nEndLen); | |
| pPara = ME_GetParagraph(p); | |
| if (!ME_StreamOutRTFHeader(pStream, dwFormat)) | |
| return FALSE; | |
| if (!ME_StreamOutRTFFontAndColorTbl(pStream, p, pEnd)) | |
| 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(pStream, ME_GetParagraph(p))) | |
| return FALSE; | |
| while(1) | |
| { | |
| switch(p->type) | |
| { | |
| case diParagraph: | |
| if (!ME_StreamOutRTFParaProps(pStream, p)) | |
| return FALSE; | |
| pPara = p; | |
| break; | |
| case diRun: | |
| if (p == pEnd && !nEndLen) | |
| break; | |
| TRACE("flags %xh\n", p->member.run.nFlags); | |
| /* TODO: emit embedded objects */ | |
| if (p->member.run.nFlags & MERF_GRAPHICS) | |
| FIXME("embedded objects are not handled\n"); | |
| if (p->member.run.nFlags & MERF_CELL) { | |
| if (!ME_StreamOutPrint(pStream, "\\cell ")) | |
| return FALSE; | |
| nChars--; | |
| } else if (p->member.run.nFlags & MERF_ENDPARA) { | |
| if (pPara->member.para.bTable) { | |
| if (!ME_StreamOutPrint(pStream, "\\row \r\n")) | |
| return FALSE; | |
| } else { | |
| if (!ME_StreamOutPrint(pStream, "\r\n\\par")) | |
| return FALSE; | |
| } | |
| nChars--; | |
| if (editor->bEmulateVersion10 && nChars) | |
| nChars--; | |
| } else { | |
| int nEnd; | |
| if (!ME_StreamOutPrint(pStream, "{")) | |
| return FALSE; | |
| TRACE("style %p\n", p->member.run.style); | |
| if (!ME_StreamOutRTFCharProps(pStream, &p->member.run.style->fmt)) | |
| return FALSE; | |
| nEnd = (p == pEnd) ? nEndLen : ME_StrLen(p->member.run.strText); | |
| if (!ME_StreamOutRTFText(pStream, p->member.run.strText->szData + nOffset, nEnd - nOffset)) | |
| return FALSE; | |
| nOffset = 0; | |
| if (!ME_StreamOutPrint(pStream, "}")) | |
| return FALSE; | |
| } | |
| break; | |
| default: /* we missed the last item */ | |
| assert(0); | |
| } | |
| if (p == pEnd) | |
| break; | |
| p = ME_FindItemFwd(p, diRunOrParagraphOrEnd); | |
| } | |
| if (!ME_StreamOutPrint(pStream, "}")) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| static BOOL | |
| ME_StreamOutText(ME_TextEditor *editor, ME_OutStream *pStream, int nStart, int nChars, DWORD dwFormat) | |
| { | |
| /* FIXME: use ME_RunOfsFromCharOfs */ | |
| ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart); | |
| int nLen; | |
| UINT nCodePage = CP_ACP; | |
| char *buffer = NULL; | |
| int nBufLen = 0; | |
| BOOL success = TRUE; | |
| if (!item) | |
| return FALSE; | |
| if (dwFormat & SF_USECODEPAGE) | |
| nCodePage = HIWORD(dwFormat); | |
| /* TODO: Handle SF_TEXTIZED */ | |
| while (success && nChars && item) { | |
| nLen = ME_StrLen(item->member.run.strText) - nStart; | |
| if (nLen > nChars) | |
| nLen = nChars; | |
| if (item->member.run.nFlags & MERF_ENDPARA) { | |
| static const WCHAR szEOL[2] = { '\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 *)(item->member.run.strText->szData + nStart), | |
| sizeof(WCHAR) * nLen); | |
| else { | |
| int nSize; | |
| nSize = WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart, | |
| nLen, NULL, 0, NULL, NULL); | |
| if (nSize > nBufLen) { | |
| FREE_OBJ(buffer); | |
| buffer = ALLOC_N_OBJ(char, nSize); | |
| nBufLen = nSize; | |
| } | |
| WideCharToMultiByte(nCodePage, 0, item->member.run.strText->szData + nStart, | |
| nLen, buffer, nSize, NULL, NULL); | |
| success = ME_StreamOutMove(pStream, buffer, nSize); | |
| } | |
| } | |
| nChars -= nLen; | |
| if (editor->bEmulateVersion10 && nChars && item->member.run.nFlags & MERF_ENDPARA) | |
| nChars--; | |
| nStart = 0; | |
| item = ME_FindItemFwd(item, diRun); | |
| } | |
| FREE_OBJ(buffer); | |
| return success; | |
| } | |
| LRESULT | |
| ME_StreamOutRange(ME_TextEditor *editor, DWORD dwFormat, int nStart, int nTo, EDITSTREAM *stream) | |
| { | |
| ME_OutStream *pStream = ME_StreamOutInit(editor, stream); | |
| if (nTo == -1) | |
| { | |
| nTo = ME_GetTextLength(editor); | |
| /* Generate an end-of-paragraph at the end of SCF_ALL RTF output */ | |
| if (dwFormat & SF_RTF) | |
| nTo++; | |
| } | |
| TRACE("from %d to %d\n", nStart, nTo); | |
| if (dwFormat & SF_RTF) | |
| ME_StreamOutRTF(editor, pStream, nStart, nTo - nStart, dwFormat); | |
| else if (dwFormat & SF_TEXT || dwFormat & SF_TEXTIZED) | |
| ME_StreamOutText(editor, pStream, nStart, nTo - nStart, dwFormat); | |
| if (!pStream->stream->dwError) | |
| ME_StreamOutFlush(pStream); | |
| return ME_StreamOutFree(pStream); | |
| } | |
| LRESULT | |
| ME_StreamOut(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream) | |
| { | |
| int nStart, nTo; | |
| if (dwFormat & SFF_SELECTION) | |
| ME_GetSelection(editor, &nStart, &nTo); | |
| else { | |
| nStart = 0; | |
| nTo = -1; | |
| } | |
| return ME_StreamOutRange(editor, dwFormat, nStart, nTo, stream); | |
| } |