| /* |
| * 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, 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 %lu 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=%lu written=%lu\n", stream->dwError, nWritten); |
| if (nWritten > (pStream->pos - nStart) || nWritten<0) { |
| FIXME("Invalid returned written size *pcb: 0x%x (%ld) instead of %ld\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 = %lu\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, fit, buffer); |
| 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; |
| 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, 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%ld\\slmult0", fmt->dyLineSpacing); |
| break; |
| case 4: |
| sprintf(props + strlen(props), "\\sl-%ld\\slmult0", fmt->dyLineSpacing); |
| break; |
| case 5: |
| sprintf(props + strlen(props), "\\sl-%ld\\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%ld", fmt->dxOffset); |
| if (fmt->dwMask & PFM_OFFSETINDENT || fmt->dwMask & PFM_STARTINDENT) |
| sprintf(props + strlen(props), "\\fi%ld", fmt->dxStartIndent); |
| if (fmt->dwMask & PFM_RIGHTINDENT) |
| sprintf(props + strlen(props), "\\ri%ld", fmt->dxRightIndent); |
| if (fmt->dwMask & PFM_SPACEAFTER) |
| sprintf(props + strlen(props), "\\sa%ld", fmt->dySpaceAfter); |
| if (fmt->dwMask & PFM_SPACEBEFORE) |
| sprintf(props + strlen(props), "\\sb%ld", fmt->dySpaceBefore); |
| if (fmt->dwMask & PFM_STYLE) |
| sprintf(props + strlen(props), "\\s%d", fmt->sStyle); |
| |
| if (fmt->dwMask & PFM_TABSTOPS) { |
| static const char *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%ld", fmt->rgxTabs[i]&0x00FFFFFF); |
| } |
| } |
| |
| |
| if (fmt->dwMask & PFM_SHADING) { |
| static const char *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%ld", fmt->yOffset); |
| else |
| sprintf(props + strlen(props), "\\dn%ld", -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%ld", 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, 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); |
| } |