| /* | |
| * 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); | |
| } |