|
|
/*
* @doc INTERNAL * * @module RTFWRIT.CPP - RichEdit RTF Writer (w/o objects) | * * This file contains the implementation of the RTF writer * for the RichEdit control, except for embedded objects, * which are handled mostly in rtfwrit2.cpp * * Authors: <nl> * Original RichEdit 1.0 RTF converter: Anthony Francisco <nl> * Conversion to C++ and RichEdit 2.0: Murray Sargent <nl> * Lots of enhancements: Brad Olenick <nl> * * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved. */
#include "_common.h"
#include "_rtfwrit.h"
#include "_objmgr.h"
#include "_coleobj.h"
#include "_font.h"
#include "_dispml.h"
#include "_version.h"
ASSERTDATA
extern const KEYWORD rgKeyword[];
//========================= Global String Constants ==================================
BYTE iCharRepANSI = ANSI_INDEX; // ToDo: make more general
#ifdef DEBUG
// Quick way to find out what went wrong: rgszParseError[ecParseError]
//
CHAR * rgszParseError[] = { "No error", "Can't convert to Unicode", // FF
"Color table overflow", // FE
"Expecting '\\rtf'", // FD
"Expecting '{'", // FC
"Font table overflow", // FB
"General failure", // FA
"Keyword too long", // F9
"Lexical analyzer initialize failed", // F8
"No memory", // F7
"Parser is busy", // F6
"PutChar() function failed", // F5
"Stack overflow", // F4
"Stack underflow", // F3
"Unexpected character", // F2
"Unexpected end of file", // F1
"Unexpected token", // F0
"UnGetChar() function failed", // EF
"Maximum text length reached", // EE
"Streaming out object failed", // ED
"Streaming in object failed", // EC
"Truncated at CR or LF", // EB
"Format-cache failure", // EA
"UTF8 not used since not all Unicode", // E9
NULL // End of list marker
};
CHAR * szDest[] = { "RTF", "Color Table", "Font Table", "Binary", "Object", "Object Class", "Object Name", "Object Data", "Field", "Field Result", "Field Instruction", "Symbol", "Paragraph Numbering", "Picture" };
#endif
// Most control-word output is done with the following printf formats
static const CHAR * rgszCtrlWordFormat[] = { // CWF_STR CWF_VAL CWF_GRP CWF_AST CWF_GRV CWF_SVAL
"\\%s", "\\%s%d", "{\\%s", "{\\*\\%s", "{\\%s%d", "\\%s%hd" };
// Special control-word formats
static const CHAR szBeginFontEntryFmt[] = "{\\f%d\\%s"; static const CHAR szBulletGroup[] = "{\\pntext\\f%d\\'B7\\tab}"; static const CHAR szBulletFmt[] = "{\\*\\pn\\pnlvl%s\\pnf%d\\pnindent%d{\\pntxtb\\'B7}}"; static const CHAR szBeginNumberGroup[] = "{\\pntext\\f%d "; static const CHAR szEndNumberGroup[] = "\\tab}"; static const CHAR szBeginNumberFmt[] = "{\\*\\pn\\pnlvl%s\\pnf%d\\pnindent%d\\pnstart%d"; static const CHAR szpntxtb[] = "{\\pntxtb(}"; static const CHAR szpntxta[] = "{\\pntxta%c}"; static const CHAR szColorEntryFmt[] = "\\red%d\\green%d\\blue%d;"; static const CHAR szEndFontEntry[] = ";}"; extern const CHAR szEndGroupCRLF[] = "}\r\n"; static const CHAR szEscape2CharFmt[] = "\\'%02x\\'%02x"; static const CHAR szLiteralCharFmt[] = "\\%c"; static const CHAR szPar[] = "\\par\r\n"; static const CHAR szPar10[] = "\r\n\\par "; static const CHAR szObjPosHolder[] = "\\objattph\\'20"; static const CHAR szDefaultFont[] = "\\deff0"; static const CHAR szHorzdocGroup[] = "{\\horzdoc}"; static const CHAR szNormalStyle[] = "{ Normal;}"; static const CHAR szHeadingStyle[] = "{\\s%d heading %d;}"; static const CHAR szEndNestRow[] = "{\\*\\nesttableprops"; static const CHAR szNestRow[] = "\\nestrow}{\\nonesttables\\par}\r\n"; static const CHAR szNestedWidthFmt[] = "\\trpaddl%d\\trpaddr%d\\trpaddfl3\\trpaddfr3\r\n"; static const CHAR szFieldStart[] = "{\\field{\\*\\fldinst{"; static const CHAR szHyperlink[] = "HYPERLINK \""; static const CHAR szFieldResult[] = "\"}}{\\fldrslt{";
#define szEscapeCharFmt &szEscape2CharFmt[6]
// Arrays of RTF control-word indices. NOTE: if any index is greater than 255,
// the corresponding array must be changed to a WORD array. The compiler
// issues a warning in such cases
const BYTE rgiszTerminators[] = { i_cell, 0, i_tab, 0, i_line, i_page, i_nestcell };
// Keep these indices in sync with the special character values in _common.h
const WORD rgiszSpecial[] = { i_enspace, // 0x2002
i_emspace, // 0x2003
0, // 0x2004
0, // 0x2005
0, // 0x2006
0, // 0x2007
0, // 0x2008
0, // 0x2009
0, // 0x200A
0, // 0x200B
i_zwnj, // 0x200C
i_zwj, // 0x200D
i_ltrmark, // 0x200E
i_rtlmark, // 0x200F
0, // 0x2010
0, // 0x2011
0, // 0x2012
i_endash, // 0x2013
i_emdash, // 0x2014
0, // 0x2015
0, // 0x2016
0, // 0x2017
i_lquote, // 0x2018
i_rquote, // 0x2019
0, // 0x201A
0, // 0x201B
i_ldblquote, // 0x201C
i_rdblquote, // 0x201D
0, // 0x201E
0, // 0x201F
0, // 0x2020
0, // 0x2021
i_bullet // 0x2022
};
const WORD rgiszEffects[] = { // Effects keywords
i_deleted, i_revised, i_disabled, i_impr, // Ordered max CFE_xx to
i_embo, i_shad, i_outl, i_v, i_caps, i_scaps, // min CFE_xx (cept i_deleted)
i_lnkd, i_protect, i_strike, i_ul, i_i, i_b // (see WriteCharFormat())
};
#define CEFFECTS ARRAY_SIZE(rgiszEffects)
const BYTE rgiszPFEffects[] = // PF effects keywords
{ // Ordered max PFE_xx to
i_collapsed, i_sbys, i_hyphpar, i_nowidctlpar, // min PFE_xx
i_noline, i_pagebb, i_keepn, i_keep, i_rtlpar }; // (see WriteParaFormat())
#define CPFEFFECTS ARRAY_SIZE(rgiszPFEffects)
const WORD rgiszUnderlines[] = { i_ulnone, i_ul, i_ulw, i_uldb, i_uld, // Std Word underlines
i_uldash, i_uldashd, i_uldashdd, i_ulwave, i_ulth, i_ulhair, i_ululdbwave, i_ulhwave, i_ulldash, i_ulthdash, i_ulthdashd, i_ulthdashdd, i_ulthd, i_ulthldash };
#define CUNDERLINES ARRAY_SIZE(rgiszUnderlines)
const BYTE rgiszFamily[] = // Font family RTF name
{ // keywords in order of
i_fnil, i_froman, i_fswiss, i_fmodern, // bPitchAndFamily
i_fscript, i_fdecor, i_ftech, i_fbidi };
#define CFAMILIES ARRAY_SIZE(rgiszFamily)
const BYTE rgiszAlignment[] = // Alignment keywords
{ // Keep in sync with
i_ql, i_qr, i_qc, i_qj // alignment constants
};
const BYTE rgiszTabAlign[] = // Tab alignment keywords
{ // Keep in sync with tab
i_tqc, i_tqr, i_tqdec // alignment constants
};
const BYTE rgiszTabLead[] = // Tab leader keywords
{ // Keep in sync with tab
i_tldot, i_tlhyph, i_tlul, i_tlth, i_tleq // leader constants
};
const BYTE rgiszNumberStyle[] = // Numbering style keywords
{ // Keep in sync with TOM
i_pndec, i_pnlcltr, i_pnucltr, // values
i_pnlcrm, i_pnucrm };
const WORD rgiszBorders[] = // Border combination keywords
{ i_box, i_brdrl, i_brdrt, i_brdrr, i_brdrb, i_trbrdrl, i_trbrdrt, i_trbrdrr, i_trbrdrb, i_clbrdrl, i_clbrdrt, i_clbrdrr, i_clbrdrb };
const BYTE rgiszBorderStyles[] = // Border style keywords
{ i_brdrdash, i_brdrdashsm, i_brdrdb, i_brdrdot, i_brdrhair, i_brdrs, i_brdrth, i_brdrtriple }; #define CBORDERSTYLES ARRAY_SIZE(rgiszBorderStyles)
const BYTE rgiszBorderEffects[] = // Border effect keywords
{ i_brdrbar, i_brdrbtw, i_brdrsh // Reverse order from bits
};
const BYTE rgiszShadingStyles[] = // Shading style keywords
{ i_bgbdiag, i_bgcross, i_bgdcross, i_bgdkbdiag, i_bgdkcross, i_bgdkdcross, i_bgdkfdiag, i_bgdkhoriz, i_bgdkvert, i_bgfdiag, i_bghoriz, i_bgvert }; #define CSHADINGSTYLES ARRAY_SIZE(rgiszShadingStyles)
// RGB with 2 bits per color type (in BGR order)
const COLORREF g_Colors[] = { RGB( 0, 0, 0), // \red0\green0\blue0
RGB( 0, 0, 255), // \red0\green0\blue255
RGB( 0, 255, 255), // \red0\green255\blue255
RGB( 0, 255, 0), // \red0\green255\blue0
RGB(255, 0, 255), // \red255\green0\blue255
RGB(255, 0, 0), // \red255\green0\blue0
RGB(255, 255, 0), // \red255\green255\blue0
RGB(255, 255, 255), // \red255\green255\blue255
RGB( 0, 0, 128), // \red0\green0\blue128
RGB( 0, 128, 128), // \red0\green128\blue128
RGB( 0, 128, 0), // \red0\green128\blue0
RGB(128, 0, 128), // \red128\green0\blue128
RGB(128, 0, 0), // \red128\green0\blue0
RGB(128, 128, 0), // \red128\green128\blue0
RGB(128, 128, 128), // \red128\green128\blue128
RGB(192, 192, 192), // \red192\green192\blue192
};
static BYTE rgcCell[MAXTABLENEST]; // Used to track proper table-row syntax
static BYTE rgiCell[MAXTABLENEST];
/*
* CRTFWrite::MapsToRTFKeywordW(wch) * * @mfunc * Returns a flag indicating whether the character maps to an RTF keyword * * @rdesc * BOOL TRUE if char maps to RTF keyword * * @devnote * Make inline since ship build only references it once */ inline BOOL CRTFWrite::MapsToRTFKeywordW( WCHAR wch) { return (!_fNCRForNonASCII || wch == BSLASH) && ( IN_RANGE(CELL, wch, SOFTHYPHEN) && // 7-AD
( wch <= CR && wch != 8 && (wch != FF || !_ped->Get10Mode()) || wch == BSLASH || IN_RANGE(LBRACE, wch, RBRACE) && wch != '|' || wch >= NBSPACE && (wch == NBSPACE || wch == SOFTHYPHEN) ) || IN_RANGE(ENSPACE, wch, BULLET) && // 2002-2022
( IN_RANGE(ENSPACE, wch, EMSPACE) || // 2002-2003
IN_RANGE(LTRMARK, wch, NBHYPHEN) && // 200E, 200F, 2011
wch != 0x2010 || IN_RANGE(ENDASH, wch, EMDASH) || // 2013-2014
IN_RANGE(LQUOTE, wch, RQUOTE) || // 2018-2019
IN_RANGE(LDBLQUOTE, wch, RDBLQUOTE) || // 201C-201D
wch == BULLET // 2022
)); }
/*
* CRTFWrite::MapsToRTFKeywordA(ch) * * @mfunc * Returns a flag indicating whether the character maps to an RTF keyword * * @rdesc * TRUE if char maps to RTF keyword * * @devnote * Make inline since ship build only references it once */ inline BOOL CRTFWrite::MapsToRTFKeywordA( char ch) { return IN_RANGE(TAB, ch, CR) && (ch != FF || !_ped->Get10Mode()) || ch == CELL || ch == BSLASH || ch == LBRACE || ch == RBRACE; }
/*
* CRTFWrite::MapToRTFKeyword(pv, cch, iCharEncoding, fQuadBackSlash) * * @mfunc * Examines the first character in the string pointed to by pv and * writes out the corresponding RTF keyword. In situations where * the first and subsequent characters map to a single keyword, we * return the number of additional characters used in the mapping. * * @rdesc * int indicates the number of additional characters used when * the mapping to an RTF keyword involves > 1 characters. */ int CRTFWrite::MapToRTFKeyword( void * pv, //@parm ptr to ansi or Unicode string
int cch, //@parm string count
int iCharEncoding, //@parm MAPTOKWD_ANSI or MAPTOKWD_UNICODE
BOOL fQuadBackSlash) //@parm If TRUE, write 4 \ for each \
{ Assert(iCharEncoding == MAPTOKWD_ANSI || iCharEncoding == MAPTOKWD_UNICODE);
WCHAR ch = ((iCharEncoding == MAPTOKWD_ANSI) ? *(char *)pv : *(WCHAR *)pv); int cchRet = 0;
Assert((iCharEncoding == MAPTOKWD_ANSI) ? MapsToRTFKeywordA(ch) : MapsToRTFKeywordW(ch));
switch(ch) { case BULLET: case EMDASH: case EMSPACE: case ENDASH: case ENSPACE: case LDBLQUOTE: case LTRMARK: case LQUOTE: case RDBLQUOTE: case RQUOTE: case RTLMARK: Assert(ch > 0xFF);
if(iCharEncoding != MAPTOKWD_ANSI) { AssertSz(rgiszSpecial[ch - ENSPACE] != 0, "CRTFWrite::WriteText(): rgiszSpecial out-of-sync"); PutCtrlWord(CWF_STR, rgiszSpecial[ch - ENSPACE]); } break;
case CELL: if(!_bTableLevel) goto lf; Assert(_iCell < _cCell); _iCell++; if(_bTableLevel > 1) // Setup to write \nestcell
ch += 6; // instead of \cell
// Fall thru to TAB, FF, VT
case TAB: case FF: case VT: Assert(TAB == CELL + 2); PutCtrlWord(CWF_STR, rgiszTerminators[ch - CELL]); break;
case CR: { if(cch) // 1 or more chars follow CR in pch
{ WCHAR ch1; WCHAR ch2 = 0;
if(iCharEncoding == MAPTOKWD_ANSI) { char *pch = (char *)pv; ch1 = pch[1]; if(cch > 1) ch2 = pch[2]; } else { WCHAR *pch = (WCHAR *)pv; ch1 = pch[1]; if(cch > 1) ch2 = pch[2]; } if(ch1 == CR && ch2 == LF) { // Translate CRCRLF to a blank (represents soft line break)
// Maybe don't want to do in 10 Mode??
PutChar(' '); cchRet = 2; break; } if(ch1 == LF) // Ignore LF after CR
{ cchRet = 1; cch--; } } } // Fall thru to LF (EOP) case
lf: case LF: PutPar(); if(_fBullet) { if(cch > 0) { if(!_nNumber) printF(szBulletGroup, _symbolFont);
else if(!_pPF->IsNumberSuppressed()) { WCHAR szNumber[CCHMAXNUMTOSTR]; _pPF->NumToStr(szNumber, ++_nNumber, fRtfWrite); printF(szBeginNumberGroup, _nFont); WritePcData(szNumber, _cpg, FALSE); printF(szEndNumberGroup); } } else _fBulletPending = TRUE; } break;
case SOFTHYPHEN: ch = '-'; goto printFLiteral;
case BSLASH: if(fQuadBackSlash) printF(szLiteralCharFmt, ch); // Fall thru to printFLiteral
case LBRACE: case RBRACE: printFLiteral: printF(szLiteralCharFmt, ch); break;
case NBSPACE: ch = '~'; goto printFLiteral;
case NBHYPHEN: ch = '_'; goto printFLiteral; } return cchRet; }
//======================== CRTFConverter Base Class ==================================
/*
* CRTFConverter::CRTFConverter() * * @mfunc * RTF Converter constructor */ CRTFConverter::CRTFConverter( CTxtRange * prg, //@parm CTxtRange for transfer
EDITSTREAM * pes, //@parm Edit stream for transfer
DWORD dwFlags, //@parm Converter flags
BOOL fRead) //@parm Initialization for a reader or writer
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFConverter::CRTFConverter");
AssertSz(prg && pes && pes->pfnCallback, "CRTFWrite::CRTFWrite: Bad RichEdit");
_prg = prg; _pes = pes; _ped = prg->GetPed(); _dwFlags = dwFlags; _ecParseError = ecNoError; _bTableLevel = 0;
if(!_ctfi) ReadFontSubInfo();
#if defined(DEBUG)
_hfileCapture = NULL;
#if !defined(NOFULLDEBUG)
if(GetProfileIntA("RICHEDIT DEBUG", "RTFCAPTURE", 0)) { char szTempPath[MAX_PATH] = "\0"; const char cszRTFReadCaptureFile[] = "CaptureRead.rtf"; const char cszRTFWriteCaptureFile[] = "CaptureWrite.rtf"; DWORD cchLength; SideAssert(cchLength = GetTempPathA(MAX_PATH, szTempPath));
// append trailing backslash if neccessary
if(szTempPath[cchLength - 1] != '\\') { szTempPath[cchLength] = '\\'; szTempPath[cchLength + 1] = 0; }
strcat(szTempPath, fRead ? cszRTFReadCaptureFile : cszRTFWriteCaptureFile); SideAssert(_hfileCapture = CreateFileA(szTempPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); } #endif // !defined(NOFULLDEBUG)
#endif // defined(DEBUG)
}
//======================== OLESTREAM functions =======================================
DWORD CALLBACK RTFPutToStream ( RTFWRITEOLESTREAM * OLEStream, //@parm OLESTREAM
const void * pvBuffer, //@parm Buffer to write
DWORD cb) //@parm Bytes to write
{ return OLEStream->Writer->WriteData ((BYTE *)pvBuffer, cb); }
//============================ CRTFWrite Class ==================================
/*
* CRTFWrite::CRTFWrite() * * @mfunc * RTF writer constructor */ CRTFWrite::CRTFWrite( CTxtRange * prg, //@parm CTxtRange to write
EDITSTREAM * pes, //@parm Edit stream to write to
DWORD dwFlags) //@parm Write flags
: CRTFConverter(prg, pes, dwFlags, FALSE) { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::CRTFWrite");
ZeroMemory(&_CF, sizeof(CCharFormat)); // Setup "previous" CF with RTF
_CF._dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;// Font info is given
_CF._yHeight = -32768; // by first font in range
// [see end of LookupFont()]
Assert(_ped); _ped->GetDefaultLCID(&_CF._lcid);
// init OleStream
RTFWriteOLEStream.Writer = this; RTFWriteOLEStream.lpstbl->Put = (DWORD (CALLBACK* )(LPOLESTREAM, const void FAR*, DWORD)) RTFPutToStream; RTFWriteOLEStream.lpstbl->Get = NULL;
_fIncludeObjects = TRUE; if((dwFlags & SF_RTFNOOBJS) == SF_RTFNOOBJS) _fIncludeObjects = FALSE;
_fNCRForNonASCII = (dwFlags & SF_NCRFORNONASCII) != 0; _fNeedDelimeter = FALSE; _nHeadingStyle = 0; // No headings found
_nNumber = 0; // No paragraph numbering yet
_pPF = NULL; _pbAnsiBuffer = NULL; _fFieldResult = FALSE; }
/*
* CRTFWrite::FlushBuffer() * * @mfunc * Flushes output buffer * * @rdesc * BOOL TRUE if successful */ BOOL CRTFWrite::FlushBuffer() { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::FlushBuffer");
LONG cchWritten;
if (!_cchBufferOut) return TRUE;
#ifdef DEBUG_PASTE
if (FromTag(tagRTFAsText)) { CHAR * pchEnd = &_pchRTFBuffer[_cchBufferOut]; CHAR chT = *pchEnd;
*pchEnd = 0; TraceString(_pchRTFBuffer); *pchEnd = chT; } #endif
_pes->dwError = _pes->pfnCallback(_pes->dwCookie, (unsigned char *)_pchRTFBuffer, _cchBufferOut, &cchWritten);
#if defined(DEBUG) && !defined(NOFULLDEBUG)
if(_hfileCapture) { DWORD cbLeftToWrite = _cchBufferOut; DWORD cbWritten2 = 0; BYTE *pbToWrite = (BYTE *)_pchRTFBuffer; while(WriteFile(_hfileCapture, pbToWrite, cbLeftToWrite, &cbWritten2, NULL) && (pbToWrite += cbWritten2, (cbLeftToWrite -= cbWritten2))); } #endif
if (_pes->dwError) { _ecParseError = ecPutCharFailed; return FALSE; } AssertSz(cchWritten == _cchBufferOut, "CRTFW::FlushBuffer: incomplete write");
_cchOut += _cchBufferOut; _pchRTFEnd = _pchRTFBuffer; // Reset buffer
_cchBufferOut = 0;
return TRUE; }
/*
* CRTFWrite::PutChar(ch) * * @mfunc * Put out the character <p ch> * * @rdesc * BOOL TRUE if successful */ BOOL CRTFWrite::PutChar( CHAR ch) //@parm char to be put
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::PutChar");
CheckDelimiter(); // If _fNeedDelimeter, may need to
// PutChar(' ')
// Flush buffer if char won't fit
if (_cchBufferOut + 1 >= cachBufferMost && !FlushBuffer()) return FALSE;
*_pchRTFEnd++ = ch; // Store character in buffer
++_cchBufferOut; return TRUE; }
/*
* CRTFWrite::PutPar() * * @mfunc * Write \r\n\par for 1.0 emulation and \par\r\n for 2.0 and later */ void CRTFWrite::PutPar() { if (_ped->Get10Mode()) Puts(szPar10, sizeof(szPar10) - 1); else Puts(szPar, sizeof(szPar) - 1); }
/*
* CRTFWrite::CheckInTable(prtp) * * @mfunc * So long as *prtp points at table-row delimiters, write relevant table * row info. * * @rdesc * 1 if successful * 0 if failed to output all relevant stuff */ BOOL CRTFWrite::CheckInTable( CRchTxtPtr *prtp, //@parm rtp at start of text to write
LONG * pcch) //@parm Remaining cch to write
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::CheckInTable");
while(*pcch) { const CParaFormat *pPF = prtp->GetPF(); if(!pPF->IsTableRowDelimiter()) break; _pPF = pPF; // Update last _pPF
CTxtPtr tp(prtp->_rpTX);
WCHAR ch = tp.GetChar(); WCHAR ch1 = tp.NextChar();
if(ch != ENDFIELD && ch != STARTFIELD || ch1 != CR) { AssertSz(FALSE, "CRTFWrite::CheckInTable: illegal table delimeter"); return FALSE; } *pcch -= prtp->Move(2); // Bypass row start or end
Assert(*pcch >= 0);
if(ch == ENDFIELD) // End of row
{ AssertSz(_bTableLevel + _bTableLevelIP == pPF->_bTableLevel, "CRTFWrite::CheckInTable: invalid table level"); _bTableLevel--; // Decrement table nesting level
_cCell = rgcCell[_bTableLevel]; // Restore _cCell for outer level
_iCell = rgiCell[_bTableLevel]; // Restore _iCell for outer level
if(_bTableLevel > 0) // Nested row:
{ // write {\*\nesttableprops...}
if(!Puts(szEndNestRow, sizeof(szEndNestRow) - 1)) return FALSE; } else // Outermost row
{ AssertSz(_bTableLevel == 0, "CRTFWrite::CheckInTable: invalid table level"); if(!_fRowHasNesting) { if(!PutCtrlWord(CWF_STR, i_row)) return FALSE; // \trowd... goes at start of row
continue; // so skip ahead
} } } else { // Start of row
_fRowHasNesting = FALSE; AssertSz(_bTableLevel < MAXTABLENEST, "CRTFWrite::CheckInTable: Need larger table nesting"); rgcCell[_bTableLevel] = _cCell; // Save current _cCell
rgiCell[_bTableLevel] = _iCell; // Save current _iCell
_iCell = 0; // Start with first cell
_cCell = pPF->_bTabCount; _bTableLevel++; // Increment table nesting level
AssertSz(_bTableLevel + _bTableLevelIP == pPF->_bTableLevel, "CRTFWrite::CheckInTable: invalid table level"); if(_bTableLevel > 1) // Nested table.
{ _fRowHasNesting = TRUE; // \trowd...
continue; // goes at end of row, so skip
} // ahead
}
LONG cTab = pPF->_bTabCount; DWORD Colors; LONG dul = 0; LONG h = pPF->_dxOffset; // \trgaph N
LONG i, j; LONG icrb, icrf; LONG k = pPF->_bAlignment; LONG x = pPF->_dxStartIndent; // Possibly shifted \trleft N
LONG z = pPF->_dyLineSpacing; // Possible \trrh N
DWORD uCell, Widths; const CELLPARMS *prgCellParms = pPF->GetCellParms();
if(pPF->_wEffects & PFE_TABLEROWSHIFTED)// Move shifted table row left
x -= h + 50; if (!PutCtrlWord(CWF_STR, i_trowd) || // Reset table properties
pPF->_wEffects & PFE_RTLPARA && !PutCtrlWord(CWF_STR, i_rtlrow) || h && !PutCtrlWord(CWF_VAL, i_trgaph, h) || x && !PutCtrlWord(CWF_VAL, i_trleft, x) || IN_RANGE(PFA_RIGHT, k, PFA_CENTER) && !PutCtrlWord(CWF_STR, k == PFA_RIGHT ? i_trqr : i_trqc) || z && !PutCtrlWord(CWF_VAL, i_trrh, z)) { return FALSE; // Signal output failed
} PutBorders(TRUE);
if(_bTableLevel > 0 && h && !printF(szNestedWidthFmt, h, h)) return FALSE;
for(i = 0; i < cTab; i++) { uCell = prgCellParms->uCell; dul += GetCellWidth(uCell); icrb = prgCellParms->GetColorIndexBackgound(); icrf = prgCellParms->GetColorIndexForegound(); if (IsTopCell(uCell) && !PutCtrlWord(CWF_STR, i_clvmgf) || IsLowCell(uCell) && !PutCtrlWord(CWF_STR, i_clvmrg) || GetCellVertAlign(uCell) && !PutCtrlWord(CWF_STR, IsCellVertAlignCenter(uCell) ? i_clvertalc : i_clvertalb) || IsVerticalCell(uCell) && !PutCtrlWord(CWF_STR, i_cltxtbrlv) || icrf && !PutCtrlWord(CWF_VAL, i_clcfpat, TranslateColorIndex(icrf, pPF)) || icrb && !PutCtrlWord(CWF_VAL, i_clcbpat, TranslateColorIndex(icrb, pPF)) || prgCellParms->bShading && !PutCtrlWord(CWF_VAL, i_clshdng, prgCellParms->bShading*50)) { return FALSE; } Colors = prgCellParms->dwColors; Widths = prgCellParms->dxBrdrWidths; prgCellParms++; if(Widths) { for(j = 0; j < 4; j++, Widths >>= 8, Colors >>= 5) { LONG w = Widths & 0xFF; LONG c = TranslateColorIndex(Colors, pPF); if(w && (!PutCtrlWord(CWF_STR, rgiszBorders[j + 9]) || !PutCtrlWord(CWF_VAL, i_brdrw, w) || !PutCtrlWord(CWF_STR, i_brdrs) || c && !PutCtrlWord(CWF_VAL, i_brdrcf, c))) { return FALSE; // Signal output failed
} } CheckDelimiter(); } x += GetCellWidth(uCell); // Translate from widths to offsets
if(!PutCtrlWord(CWF_VAL, i_cellx, x)) return FALSE; // Signal output failed
} if(ch == ENDFIELD) // End of row
{ if(_bTableLevel) // Nested row
{ if(!Puts(szNestRow, sizeof(szNestRow) - 1)) return FALSE; } else if(_fRowHasNesting && !PutCtrlWord(CWF_STR, i_row)) return FALSE; } } return TRUE; }
/*
* CRTFWrite::PutBorders(fInTable) * * @mfunc * If any borders are defined, output their control words * * @rdesc * error code */ EC CRTFWrite::PutBorders( BOOL fInTable) { DWORD Widths = _pPF->_wBorderWidth; BOOL fBox = _pPF->_wEffects & PFE_BOX;
if(Widths || fBox) { DWORD Colors = _pPF->_dwBorderColor; DWORD dwEffects = Colors >> 20; LONG i = 1, iMax = 4; // NonBox for loop limits
LONG j, k; DWORD Spaces = _pPF->_wBorderSpace; DWORD Styles = _pPF->_wBorders;
if(fBox) i = iMax = 0; // For box, only write one set
for( ; i <= iMax; i++, Spaces >>= 4, Styles >>= 4, Widths >>= 4, Colors >>= 5) { if(!(Widths & 0xF) && !fBox) // No width, so no border
continue;
j = TWIPS_PER_POINT*(Spaces & 0xF); k = Colors & 0x1F; if (!PutCtrlWord(CWF_STR, rgiszBorders[i + 4*fInTable]) || !PutCtrlWord(CWF_STR, rgiszBorderStyles[Styles & 0xF]) || !PutCtrlWord(CWF_VAL, i_brdrw, 5*(Widths & 0xF)) || k && !PutCtrlWord(CWF_VAL, i_brdrcf, LookupColor(g_Colors[k-1]) + 1) || j && !PutCtrlWord(CWF_VAL, i_brsp, j)) { break; } for(j = 3; j--; dwEffects >>= 1) // Output border effects
{ if (dwEffects & 1 && !PutCtrlWord(CWF_STR, rgiszBorderEffects[j])) { break; } } CheckDelimiter(); // Output a ' '
} } return _ecParseError; }
/*
* CRTFWrite::Puts(sz, cb) * * @mfunc * Put out the string <p sz> * * @rdesc * BOOL TRUE if successful */ BOOL CRTFWrite::Puts( CHAR const * sz, LONG cb) //@parm String to be put
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::Puts");
if(*sz == '\\' || *sz == '{' || *sz == ' ') _fNeedDelimeter = FALSE;
CheckDelimiter(); // If _fNeedDelimeter, may need to
// PutChar(' ')
// Flush buffer if string won't fit
if (_cchBufferOut + cb >= cachBufferMost && !FlushBuffer()) return FALSE;
if (cb >= cachBufferMost) // If buffer still can't handle string,
{ // we have to write string directly
LONG cbWritten;
#ifdef DEBUG_PASTE
if (FromTag(tagRTFAsText)) TraceString(sz); #endif
_pes->dwError = _pes->pfnCallback(_pes->dwCookie, (LPBYTE) sz, cb, &cbWritten); _cchOut += cbWritten;
#if defined(DEBUG) && !defined(NOFULLDEBUG)
if(_hfileCapture) { DWORD cbLeftToWrite = cb; DWORD cbWritten2 = 0; BYTE *pbToWrite = (BYTE *)sz; while(WriteFile(_hfileCapture, pbToWrite, cbLeftToWrite, &cbWritten2, NULL) && (pbToWrite += cbWritten2, (cbLeftToWrite -= cbWritten2))); } #endif
if (_pes->dwError) { _ecParseError = ecPutCharFailed; return FALSE; } AssertSz(cbWritten == cb, "CRTFW::Puts: incomplete write"); } else { CopyMemory(_pchRTFEnd, sz, cb); // Put string into buffer for later
_pchRTFEnd += cb; // output
_cchBufferOut += cb; }
return TRUE; }
/*
* CRTFWrite::PutCtrlWord(iFormat, iCtrl, iValue) * * @mfunc * Put control word with rgKeyword[] index <p iCtrl> and value <p iValue> * using format rgszCtrlWordFormat[<p iFormat>] * * @rdesc * TRUE if successful * * @devnote * Sets _fNeedDelimeter to flag that next char output must be a control * word delimeter, i.e., not alphanumeric (see PutChar()). */ BOOL CRTFWrite::PutCtrlWord( LONG iFormat, //@parm Format index into rgszCtrlWordFormat
LONG iCtrl, //@parm Index into Keyword array
LONG iValue) //@parm Control-word parameter value. If missing,
{ // 0 is assumed
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::PutCtrlWord");
BOOL bRet; CHAR szT[60]; LONG cb;
if(iFormat == CWF_SVAL) // Use a SHORT to get 16-bit signed
{ // values for 32768 thru 65535
SHORT sValue = iValue; // Make this endian independent
cb = sprintf(szT, (char *)rgszCtrlWordFormat[CWF_SVAL], rgKeyword[iCtrl].szKeyword, sValue); } else { cb = sprintf(szT, (char *)rgszCtrlWordFormat[iFormat], rgKeyword[iCtrl].szKeyword, iValue); } _fNeedDelimeter = FALSE; bRet = Puts(szT, cb); _fNeedDelimeter = TRUE; // Ensure next char isn't
// alphanumeric
return bRet; }
/*
* CRTFWrite::printF(szFmt, ...) * * @mfunc * Provide formatted output * * @rdesc * TRUE if successful */ BOOL _cdecl CRTFWrite::printF( CONST CHAR * szFmt, //@parm Format string for printf()
...) //@parmvar Parameter list
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::printF"); va_list marker; CHAR szT[60];
va_start(marker, szFmt); int cb = W32->WvsprintfA(60, szT, szFmt, marker); va_end(marker);
return Puts(szT, cb); }
/*
* CRTFWrite::WritePcData(szData, nCodePage, fIsDBCS) * * @mfunc * Write out the string <p szData> as #PCDATA where any special chars * are protected by leading '\\'. * * @rdesc * EC (_ecParseError) */ EC CRTFWrite::WritePcData( const WCHAR * szData, //@parm #PCDATA string to write
INT nCodePage, //@parm code page default value CP_ACP
BOOL fIsDBCS) //@parm szData is a DBCS string stuffed into Unicode buffer
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WritePcData");
BYTE ch; BOOL fMissingCodePage; BOOL fMultiByte; const BYTE *pch; const char *pchToDBCSDefault = NULL; BOOL * pfUsedDefault = NULL;
if(IsUTF8) nCodePage = CP_UTF8;
if(!*szData) return _ecParseError;
int DataSize = wcslen(szData) + 1; int BufferSize = DataSize * 3; char *pBuffer = (char *)PvAlloc(BufferSize, GMEM_ZEROINIT); if(!pBuffer) return ecNoMemory;
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
// When WCTMB fails to convert a char, the following default
// char is used as a placeholder in the string being converted
const char chToDBCSDefault = 0; BOOL fUsedDefault;
pchToDBCSDefault = &chToDBCSDefault; pfUsedDefault = &fUsedDefault; #endif
int cchRet = WCTMB(fIsDBCS ? INVALID_CODEPAGE : nCodePage, 0, szData, -1, pBuffer, BufferSize, pchToDBCSDefault, pfUsedDefault, &fMissingCodePage); Assert(cchRet > 0);
if(!fIsDBCS && fMissingCodePage && nCodePage != CP_ACP) { // Here, the system could not convert the Unicode string because the
// code page is not installed on the system. Fallback to CP_ACP.
cchRet = WCTMB(CP_ACP, 0, szData, -1, pBuffer, BufferSize, pchToDBCSDefault, pfUsedDefault, &fMissingCodePage); Assert(cchRet > 0);
nCodePage = CP_ACP; }
AssertSz(!fUsedDefault, "CRTFWrite::WritePcData(): Found character in " "control text which cannot be converted from " "Unicode"); if(cchRet <= 0) { _ecParseError = ecCantUnicode; goto CleanUp; }
BufferSize = cchRet;
fMultiByte = (BufferSize > DataSize) || fIsDBCS || fMissingCodePage; pch = (BYTE *)pBuffer; ch = *pch; // If _fNeedDelimeter, may need to PutChar(' ')
CheckDelimiter(); while (!_ecParseError && (ch = *pch++)) { if(fMultiByte && *pch && nCodePage != CP_UTF8 && GetTrailBytesCount(ch, nCodePage)) printF(szEscape2CharFmt, ch, *pch++); // Output DBC pair
else { if(ch == LBRACE || ch == RBRACE || ch == BSLASH) printF(szLiteralCharFmt, ch);
else if(ch < 32 || ch == ';' || ch > 127) printF(szEscapeCharFmt, ch);
else PutChar(ch); } }
CleanUp: FreePv(pBuffer); return _ecParseError; }
/*
* CRTFWrite::TranslateColorIndex (icr, pPF) * * @mfunc * Returns CRTFWrite::_colors[] index corresponding to backing-store * color index icr * * @rdesc * _colors[] index corresponding to backing-store color index icr */ LONG CRTFWrite::TranslateColorIndex( LONG icr, //@parm Color index
const CParaFormat *pPF) //@parm CF for two custom colors
{ icr &= 0x1F; // Kill possible higher-order bits
if(!IN_RANGE(1, icr, 18)) return 0; // Autocolor
if(IN_RANGE(1, icr, 16)) // One of standard 16 colors
return LookupColor(g_Colors[icr - 1]) + 1;
return LookupColor((icr == 17) ? pPF->_crCustom1 : pPF->_crCustom2) + 1; }
/*
* CRTFWrite::LookupColor(cr) * * @mfunc * Return color-table index for color referred to by <p cr>. * If a match isn't found, an entry is added. * * @rdesc * LONG Index into colortable * <lt> 0 on error */ LONG CRTFWrite::LookupColor( COLORREF cr) //@parm colorref to look for
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::LookupColor");
LONG Count = _colors.Count(); LONG iclrf; COLORREF * pclrf;
for(iclrf = 0; iclrf < Count; iclrf++) // Look for color
if(_colors.GetAt(iclrf) == cr) return iclrf;
pclrf = _colors.Add(1, NULL); // If we couldn't find it,
if(!pclrf) // add it to color table
return -1; *pclrf = cr;
return iclrf; }
/*
* CRTFWrite::LookupFont(pCF) * * @mfunc * Returns index into font table for font referred to by * CCharFormat *<p pCF>. If a match isn't found, an entry is added. * * @rdesc * SHORT Index into fonttable * <lt> 0 on error */ LONG CRTFWrite::LookupFont( CCharFormat const * pCF) //@parm CCharFormat holding font name
{ // to look up
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::LookupFont");
LONG Count = _fonts.Count(); LONG itf; TEXTFONT * ptf; for(itf = 0; itf < Count; itf++) { // Look for font
ptf = _fonts.Elem(itf); if (ptf->bPitchAndFamily == pCF->_bPitchAndFamily &&// of same pitch,
ptf->iCharRep == pCF->_iCharRep && // char rep, and
ptf->iFont == pCF->_iFont) // name
{ return itf; // Found it
} } ptf = _fonts.Add(1, NULL); // Didn't find it:
if(!ptf) // add to table
return -1;
ptf->bPitchAndFamily = pCF->_bPitchAndFamily; ptf->iCharRep = pCF->_iCharRep; ptf->sCodePage = (short)CodePageFromCharRep(pCF->_iCharRep); ptf->iFont = pCF->_iFont; ptf->fNameIsDBCS = (pCF->_dwEffects & CFE_FACENAMEISDBCS) != 0;
#if 0
// Bug1523 - (BradO) I removed this section of code so that a /fN tag is always
// emitted for the first run of text. In theory, we should be able to
// assume that the first run of text would carry the default font.
// It turns out that when reading RTF, Word doesn't use anything predictable
// for the font of the first run of text in the absence of an explicit /fN,
// thus, we have to explicitly emit a /fN tag for the first run of text.
if(!Count) // 0th font is
{ // default \deff0
_CF.bPitchAndFamily = pCF->bPitchAndFamily; // Set "previous"
_CF.bCharSet = pCF->bCharSet; // CF accordingly
wcscpy(_CF.szFaceName, pCF->szFaceName); } #endif
return itf; }
/*
* CRTFWrite::BuildTables(&rtp, cch, &fNameIsDBCS) * * @mfunc * Build font and color tables for write range of length <p cch> * * @rdesc * EC The error code */ EC CRTFWrite::BuildTables( CRchTxtPtr &rtp, //@parm rtp at cpMin of _prg
LONG cch, //@parm # chars in write range
BOOL& fNameIsDBCS) //@parm OUT =TRUE if CFE_FACENAMEISDBCS run in range
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::BuildTables");
LONG cchTotal = cch; LONG i; CCFRunPtr rpCF(rtp); CPFRunPtr rpPF(rtp);
fNameIsDBCS = FALSE;
while(cch > 0) { const CCharFormat *pCF = rpCF.GetCF(); DWORD dwEffects = pCF->_dwEffects; Assert(pCF);
if(dwEffects & CFE_FACENAMEISDBCS) fNameIsDBCS = TRUE;
if (dwEffects & (CFE_RUNISDBCS | CFE_FACENAMEISDBCS) && _dwFlags & SF_USECODEPAGE && HIWORD(_dwFlags) == CP_UTF8) { // Kill UTF8, since text
_dwFlags &= 0xFFFF & ~SF_USECODEPAGE; // isn't all Unicode
}
// Look up character-format *pCF's font and color. If either isn't
// found, it is added to appropriate table. Don't lookup color
// for CCharFormats with auto-color
if (LookupFont(pCF) < 0 || (!(dwEffects & CFE_AUTOCOLOR) && LookupColor(pCF->_crTextColor) < 0) || (!(dwEffects & CFE_AUTOBACKCOLOR) && LookupColor(pCF->_crBackColor) < 0) || ( (dwEffects & CFE_LINK) && LookupColor(g_Colors[1]) < 0)) { break; } if(!rpCF.IsValid()) break; cch -= rpCF.GetCchLeft(); rpCF.NextRun(); }
const CParaFormat *pPF;
// Now look for bullets; if found, then we need to include
// the "Symbol" font. Also check on border and shading colors
cch = cchTotal; _symbolFont = 0;
_bTableLevelIP = rtp.GetPF()->_bTableLevel; if(_bTableLevelIP && rtp._rpTX.IsAtTRD(STARTFIELD)) _bTableLevelIP--;
while(cch > 0) { pPF = rpPF.GetPF(); if(!pPF) goto CacheError; if(pPF->_wNumbering == PFN_BULLET && !_symbolFont) { CCharFormat CF;
// Be sure these choices agree with those in CMeasurer::GetCcsBullet()
// and that LookupFont() doesn't access any other CF members.
CF._iFont = IFONT_SYMBOL; CF._iCharRep = SYMBOL_INDEX; CF._bPitchAndFamily = FF_DONTCARE;
// Save Font index for Symbol. Reset it to 0 if LookupFont
// returns error.
_symbolFont = LookupFont(&CF); _symbolFont = max(_symbolFont, 0); } WORD Widths = pPF->_wBorderWidth; DWORD Colors = pPF->_dwBorderColor & 0xFFFFF;
while(Widths && Colors) { i = Colors & 0x1F; if(i && (Widths & 0xF)) LookupColor(g_Colors[i - 1]);
Widths >>= 4; Colors >>= 5; } i = (pPF->_wShadingStyle >> 6) & 31; // Shading forecolor
if(i) LookupColor(g_Colors[i - 1]); i = pPF->_wShadingStyle >> 11; // Shading backcolor
if(i) LookupColor(g_Colors[i - 1]);
if(IsHeadingStyle(pPF->_sStyle) && pPF->_sStyle < _nHeadingStyle) _nHeadingStyle = pPF->_sStyle;
if(pPF->IsTableRowDelimiter()) { const CELLPARMS *prgCellParms = pPF->GetCellParms(); for(LONG cCell = pPF->_bTabCount; cCell--; prgCellParms++) { for(DWORD dwColors = prgCellParms->dwColors; dwColors; dwColors >>= 5) TranslateColorIndex(dwColors, pPF); } }
if(!rpPF.IsValid()) break; cch -= rpPF.GetCchLeft(); rpPF.NextRun(); } return _ecParseError;
CacheError: _ecParseError = ecFormatCache; return ecFormatCache; // Access to CF/PF cache failed
}
/*
* CRTFWrite::WriteFontTable() * * @mfunc * Write out font table * * @rdesc * EC The error code */ EC CRTFWrite::WriteFontTable() { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteFontTable");
LONG Count = _fonts.Count(); int itf; int m; int pitch; TEXTFONT * ptf; char * szFamily; const WCHAR * szName; WCHAR * szTaggedName;
if(!Count || !PutCtrlWord(CWF_GRP, i_fonttbl)) // Start font table group
goto CleanUp;
for (itf = 0; itf < Count; itf++) { ptf = _fonts.Elem(itf);
// if (ptf->sCodePage)
// if (! PutCtrlWord(CWF_VAL, i_cpg, ptf->sCodePage ) )
// goto CleanUp;
// Define font family
m = ptf->bPitchAndFamily >> 4; szFamily = rgKeyword[rgiszFamily[m < CFAMILIES ? m : 0]].szKeyword; szName = GetFontName(ptf->iFont); szTaggedName = NULL;
// Check to see if this is a tagged font
if (!ptf->iCharRep || !FindTaggedFont(szName, ptf->iCharRep, &szTaggedName)) { szTaggedName = NULL; }
pitch = ptf->bPitchAndFamily & 0xF; // Write font
if (!printF(szBeginFontEntryFmt, itf, szFamily)) // entry, family,
goto CleanUp; _fNeedDelimeter = TRUE; if (pitch && !PutCtrlWord(CWF_VAL, i_fprq, pitch)) // and pitch
goto CleanUp;
if(!ptf->sCodePage && ptf->iCharRep) ptf->sCodePage = (short)CodePageFromCharRep(ptf->iCharRep);
// Write charset. Win32 uses ANSI_CHARSET to mean the default Windows
// character set, so find out what it really is
extern BYTE iCharRepANSI;
if(ptf->iCharRep != DEFAULT_INDEX) { BYTE iCharRep = ptf->iCharRep; BOOL fWroteCharSet = TRUE;
if(iCharRep == PC437_INDEX || iCharRep >= NCHARSETS) { fWroteCharSet = FALSE; iCharRep = iCharRep >= NCHARSETS ? DEFAULT_INDEX : ANSI_INDEX; } if(!PutCtrlWord(CWF_VAL, i_fcharset, CharSetFromCharRep(iCharRep))) goto CleanUp;
// Skip \cpgN output if we've already output a \fcharsetN tag.
// This is to accomodate RE 1.0, which can't handle some \cpgN
// tags properly. Specifically, when RE 1.0 parses the \cpgN tag
// it looks up the corresponding charset value. Turns out its
// codepage/charset table is incomplete so it maps some codepages
// to charset 0, trouncing the previously read \fcharsetN value.
if (fWroteCharSet) goto WroteCharSet; }
if(ptf->sCodePage && !PutCtrlWord(CWF_VAL, i_cpg, ptf->sCodePage)) goto CleanUp;
WroteCharSet: if(szTaggedName) { // Have a tagged font: write out group with real name followed by tagged name
if(!PutCtrlWord(CWF_AST, i_fname) || WritePcData(szName, ptf->sCodePage, ptf->fNameIsDBCS) || !Puts(szEndFontEntry, sizeof(szEndFontEntry) - 1) || WritePcData(szTaggedName, ptf->sCodePage, ptf->fNameIsDBCS) || !Puts(szEndFontEntry, sizeof(szEndFontEntry) - 1)) { goto CleanUp; } } else if(WritePcData(szName, ptf->sCodePage, ptf->fNameIsDBCS) || !Puts(szEndFontEntry, sizeof(szEndFontEntry) - 1)) // If non-tagged font just write name out
{ goto CleanUp; } } Puts(szEndGroupCRLF, sizeof(szEndGroupCRLF) - 1); // End font table group
CleanUp: return _ecParseError; }
/*
* CRTFWrite::WriteColorTable() * * @mfunc * Write out color table * * @rdesc * EC The error code */ EC CRTFWrite::WriteColorTable() { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteColorTable");
LONG Count = _colors.Count(); COLORREF clrf; LONG iclrf;
if (!Count || !PutCtrlWord(CWF_GRP, i_colortbl) // Start color table group
|| !PutChar(';')) // with null first entry
{ goto CleanUp; }
for(iclrf = 0; iclrf < Count; iclrf++) { clrf = _colors.GetAt(iclrf); if (!printF(szColorEntryFmt, GetRValue(clrf), GetGValue(clrf), GetBValue(clrf))) goto CleanUp; }
Puts(szEndGroupCRLF,sizeof(szEndGroupCRLF) -1); // End color table group
CleanUp: return _ecParseError; }
/*
* CRTFWrite::WriteCharFormat(prtp, cch, nCodePage) * * @mfunc * Write deltas between CCharFormat <p pCF> and the previous CCharFormat * given by _CF, and then set _CF = *<p pCF>. * * @rdesc * cch *prtp moved or < 0 if error * * @devnote * For optimal output, could write \\plain and use deltas relative to * \\plain if this results in less output (typically only one change * is made when CF changes, so less output results when compared to * previous CF than when compared to \\plain). */ LONG CRTFWrite::WriteCharFormat( CRchTxtPtr *prtp, //@parm Ptr to rich-text ptr at current cp
LONG cch, //@parm Remaining cch to write
LONG nCodePage) //@parm CodePage to use in writing hyperlinks
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteCharFormat");
LONG cp = prtp->GetCp(); const CCharFormat * pCF = prtp->GetCF(); DWORD dwEffects = pCF->_dwEffects; // Current effects
DWORD dwChanges = _CF._dwEffects; // Previous effects (will be
BOOL fAutoURLDetect = (prtp->GetPed()->GetDetectURL() != 0);// changed between them)
BOOL fFieldClosed = FALSE; BOOL fPrecededByLink = FALSE; LONG i; LONG iFormat; LONG iValue; // Control-word value
LONG i_sz; // Temp ctrl string index
LONG yOffset = pCF->_yOffset;
if(cp) { fPrecededByLink = dwChanges & CFE_LINK; if(cp == _prg->GetCp()) { prtp->_rpCF.AdjustBackward(); fPrecededByLink = prtp->GetCF()->_dwEffects & CFE_LINK; prtp->_rpCF.AdjustForward(); } } if(_fFieldResult && fPrecededByLink && !(dwEffects & CFE_LINK)) { // End of hyperlink
_fFieldResult = FALSE; if (_nFieldFont != -1 && !PutCtrlWord(CWF_VAL, i_f, _nFieldFont)) return FALSE; if(!Puts("}}}", 3)) // Close \fldrslt and \field
return -1; // Error return
fFieldClosed = TRUE; } BOOL fStartOfLink = !_fFieldResult && !fPrecededByLink && (dwEffects & CFE_LINK);
if(fStartOfLink) { if(dwEffects & CFE_LINKPROTECTED) dwEffects &= ~CFE_HIDDEN; else if(!fAutoURLDetect) // Only output our links for
fStartOfLink = FALSE; // now (else have to validate
} // client spec'd links)
DWORD UType1 = _CF._bUnderlineType; // Previous underline type
if(UType1 >= CUNDERLINES) // Special underlines are
dwChanges &= ~CFE_UNDERLINE; // not written out, so
// claim they're not on
DWORD UType2 = pCF->_bUnderlineType; // Current underline type
if(UType2 >= CUNDERLINES) // _bUnderlineType for
dwEffects &= ~CFE_UNDERLINE; // specials has non0
// high nibble
COLORREF cr = pCF->_crTextColor; BOOL fSetLinkAttributes = FALSE;
if((dwEffects & (CFE_LINK | CFE_LINKPROTECTED)) == CFE_LINK && !fStartOfLink && !_fFieldResult && fAutoURLDetect) { fSetLinkAttributes = TRUE; // Incomplete AutoURL
dwEffects |= CFE_UNDERLINE; // link. Give it blue &
dwEffects &= ~CFE_AUTOCOLOR; // underline attributes
cr = g_Colors[1]; // Blue
} dwChanges ^= dwEffects; // Now dwChanges is the
// diff between effects
if (dwChanges & CFE_AUTOCOLOR || // Change in autocolor
cr != _CF._crTextColor) // or text color
{ iValue = 0; // Default autocolor
if(!(dwEffects & CFE_AUTOCOLOR)) // Make that text color
iValue = LookupColor(cr) + 1; if(!PutCtrlWord(CWF_VAL, i_cf, iValue)) return -1; // Error return
}
if (dwChanges & CFE_AUTOBACKCOLOR || // Change in autobackcolor
pCF->_crBackColor != _CF._crBackColor) // or backcolor
{ iValue = 0; // Default autobackcolor
if(!(dwEffects & CFE_AUTOBACKCOLOR)) // Make that back color
iValue = LookupColor(pCF->_crBackColor) + 1; if(!PutCtrlWord(CWF_VAL, i_highlight, iValue)) return -1; // Error return
}
if (pCF->_lcid != _CF._lcid && !PutCtrlWord(CWF_VAL, i_lang, LANGIDFROMLCID((WORD)pCF->_lcid)) || pCF->_sSpacing != _CF._sSpacing && !PutCtrlWord(CWF_VAL, i_expndtw, pCF->_sSpacing) || /* FUTURE (alexgo): This code is incorrect and we don't
yet handle the Style table. We may want to support this better in a future version. pCF->_sStyle != _CF._sStyle && pCF->_sStyle > 0 && !PutCtrlWord(CWF_VAL, i_cs, pCF->_sStyle) || */ pCF->_bAnimation != _CF._bAnimation && !PutCtrlWord(CWF_VAL, i_animtext, pCF->_bAnimation) || /* FUTURE (alexgo): this code doesn't work yet, as we don't
output the revision table. We may want to support this better in a future version pCF->_bRevAuthor!= _CF._bRevAuthor && !PutCtrlWord(CWF_VAL, i_revauth, pCF->_bRevAuthor) || */ pCF->_wKerning != _CF._wKerning && !PutCtrlWord(CWF_VAL, i_kerning, pCF->_wKerning/10) ) { return -1; // Error return
}
// Handle all underline types. Special underline types (nonzero high
// nibble in CCharFormat::_bUnderlineType) are considered to be no
// underline and have their UType set equal to 0 above and underline
// effect bits reset to 0.
if ((dwChanges & CFM_UNDERLINE) || (dwEffects & CFE_UNDERLINE) && UType1 != UType2) { dwChanges &= ~CFE_UNDERLINE; // Suppress underline
i = dwEffects & CFE_UNDERLINE ? UType2: 0; // action in next for()
if(!PutCtrlWord(CWF_STR, rgiszUnderlines[i])) return -1; // Error return
} // This must be before next stuff
if(dwChanges & (CFM_SUBSCRIPT | CFM_SUPERSCRIPT))// change in sub/sup status
{ i_sz = dwEffects & CFE_SUPERSCRIPT ? i_super : dwEffects & CFE_SUBSCRIPT ? i_sub : i_nosupersub; if(!PutCtrlWord(CWF_STR, i_sz)) return -1; // Error return
}
if(dwChanges & CFE_DELETED) // Insert deleted at high
{ // end of bit string
dwChanges |= CFE_REVISED << 1; if(dwEffects & CFE_DELETED) dwEffects |= CFE_REVISED << 1; }
dwChanges &= ((1 << CEFFECTS) - 1) & ~CFE_LINK; // Output keywords for
for(i = CEFFECTS; // effects that changed
dwChanges && i--; // rgszEffects[] contains
dwChanges >>= 1, dwEffects >>= 1) // effect keywords in
{ // order max CFE_xx to
if(dwChanges & 1) // min CFE-xx
{ // Change from last call
iValue = dwEffects & 1; // If effect is off, write
iFormat = iValue ? CWF_STR : CWF_VAL; // a 0; else no value
if(!PutCtrlWord(iFormat, rgiszEffects[i], iValue)) return -1; // Error return
} }
if(yOffset != _CF._yOffset) // Change in base line
{ // position
yOffset /= 10; // Default going to up
i_sz = i_up; iFormat = CWF_VAL; if(yOffset < 0) // Make that down
{ i_sz = i_dn; yOffset = -yOffset; } if(!PutCtrlWord(iFormat, i_sz, yOffset)) return -1; // Error return
}
if (pCF->_bPitchAndFamily != _CF._bPitchAndFamily || // Change in font
pCF->_iCharRep != _CF._iCharRep || pCF->_iFont != _CF._iFont || fFieldClosed) { iValue = LookupFont(pCF); if(iValue < 0 || !PutCtrlWord(CWF_VAL, i_f, iValue)) return -1; // Error return
// RichEdit encodes the current direction in iCharRep, but Word likes
// to know explicitly, so output the appropriate choice of \rtlch or
// \ltrch if the direction changes
BOOL fRTLCharRep = IsRTLCharRep(pCF->_iCharRep);
if (fRTLCharRep != IsRTLCharRep(_CF._iCharRep) && !PutCtrlWord(CWF_STR, fRTLCharRep ? i_rtlch : i_ltrch)) { return -1; // Error return
}
if (_fFieldResult && pCF->_iCharRep) // Save non-Ansi font index during FieldResult.
_nFieldFont = iValue; // This is to make RE30 hyperlink code happy.
} if(pCF->_yHeight != _CF._yHeight || fFieldClosed)// Change in font size
{ iValue = (pCF->_yHeight + (pCF->_yHeight > 0 ? 5 : -5))/10; if(!PutCtrlWord(CWF_VAL, i_fs, iValue)) return -1; // Error return
} _CF = *pCF; // Update previous CCharFormat
if(!fStartOfLink) // Not start of hyperlink
{ if(fSetLinkAttributes) { _CF._dwEffects = dwEffects; _CF._crTextColor = cr; } return 0; // Normal return
}
// Start of hyperlink field: write out starting field info
BOOL fQuadBackSlash = TRUE; // Defaults for raw URL
DWORD dwMask = CFE_LINK; CTxtPtr tp(prtp->_rpTX);
if(pCF->_dwEffects & CFE_LINKPROTECTED) // \fldinst info should be
{ // in backing store
_CF._dwEffects &= ~CFE_HIDDEN; if(tp.FindText(tomForward, FR_DOWN, L"HYPERLINK", 9) == -1) return -1; // Error return: no link
unsigned ch; while((ch = tp.GetChar()) == ' ' || ch == '\"') tp.Move(1); ch = tp.GetPrevChar(); if(ch != '\"') // Not quoted, so don't need
fQuadBackSlash = FALSE; // quad backslashes
prtp->SetCp(tp.GetCp()); // Advance to start of URL
dwMask = CFE_HIDDEN; // URL terminated when text
} // not hidden
CFormatRunPtr rp(prtp->_rpCF); LONG cchLink = 0; while(_ped->GetCharFormat(rp.GetFormat())->_dwEffects & dwMask) { cchLink += rp.GetCchLeft(); if(!rp.NextRun()) break; } if(cch < cchLink) // Link is only partially
return 0; // selected, so don't
// write hyperlink field
if(!Puts(szFieldStart, sizeof(szFieldStart) - 1) || !Puts(szHyperlink, sizeof(szHyperlink) - 1)) { return -1; // Error return
}
// Write CFE_LINK field
prtp->SetCp(tp.GetCp()); LONG nCodePagePrev = nCodePage; while(cchLink > 0) { LONG cchCF = cchLink; // For UTF-8, output whole
if(!IsUTF8) // link at once. Else one
{ // CF run at a time
cchCF = prtp->GetCchLeftRunCF(); cchCF = min(cchCF, cchLink); const CCharFormat *pCF = prtp->GetCF(); nCodePage = CodePageFromCharRep(pCF->_iCharRep); if(!nCodePage) nCodePage = 1252; if(nCodePage == CP_ACP && (_dwFlags & SF_USECODEPAGE)) nCodePage = HIWORD(_dwFlags); if(nCodePage != nCodePagePrev) { iValue = LookupFont(pCF); if(iValue < 0 || !PutCtrlWord(CWF_VAL, i_f, iValue)) return -1; // Error return
nCodePagePrev = nCodePage; } } while(cchCF > 0) { LONG cchChunk; const WCHAR *pch = prtp->_rpTX.GetPch(cchChunk);
cchChunk = min(cchChunk, cchCF); cchChunk = min(cchChunk, cachBufferMost); if(WriteText(cchChunk, pch, nCodePage, FALSE, fQuadBackSlash)) return -1; // Error return
prtp->Move(cchChunk); cchCF -= cchChunk; cchLink -= cchChunk; } } i = 0; LONG cchMove = 0; if(dwMask == CFE_HIDDEN) // Friendly URL
{ if(fQuadBackSlash) // Bypass quote since it's
i = 1; // already output
cchMove = prtp->GetCp() - cp; // Tell caller cch that prtp moved
} else prtp->SetCp(cp);
if(!Puts(szFieldResult + i, sizeof(szFieldResult) - i - 1) || dwMask == CFE_LINK && // RichEdit autoURL: use
(!PutCtrlWord(CWF_STR, i_ul, 0) || // underline & blue
!PutCtrlWord(CWF_VAL, i_cf, LookupColor(g_Colors[1]) + 1))) { return -1; } _nFieldFont = -1; _fFieldResult = TRUE; // Signal to look for end of
return cchMove; // URL result field
}
/*
* CRTFWrite::WriteParaFormat(prtp, pcch) * * @mfunc * Write out attributes specified by the CParaFormat <p pPF> relative * to para defaults (probably produces smaller output than relative to * previous para format and let's you redefine tabs -- no RTF kill * tab command except \\pard) * * @rdesc * EC The error code */ EC CRTFWrite::WriteParaFormat( CRchTxtPtr *prtp, //@parm Ptr to rich-text ptr at current cp
LONG * pcch) //@parm Remaining cch to write
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteParaFormat");
Assert(_ped);
//if(!_fRangeHasEOP) // Don't write para info if
// return _ecParseError; // range has no EOPs
if(!CheckInTable(prtp, pcch) || !*pcch) // If table delimeter para, write
return _ecParseError; // table info and advance prtp
// past 2-character delimeter
const CParaFormat * pPFPrev = _pPF; const CParaFormat * pPF = _pPF = prtp->GetPF(); LONG c; // Temporary count
LONG cTab = pPF->_bTabCount; DWORD dwEffects; DWORD dwRule = pPF->_bLineSpacingRule; LONG dy = pPF->_dyLineSpacing; LONG i_t, i, j, k; LONG tabAlign, tabLead, tabPos; LONG lDocDefaultTab = _ped->GetDefaultTab(); const LONG *prgxTabs = NULL;
if(!lDocDefaultTab) lDocDefaultTab = lDefaultTab;
AssertSz(cTab >= 0 && cTab <= MAX_TAB_STOPS, "CRTFW::WriteParaFormat: illegal cTabCount");
// EVIL HACK ALERT! - Exchange's IMC keys on the \protect tag when it does
// its reply-ticking for mail being sent to Internet recipients.
// Paragraphs following a \pard and containing a \protect tag are
// reply-ticked, so we must ensure that each \pard in a protected range
// is followed by a \protect tag.
if (_CF._dwEffects & CFE_PROTECTED && !PutCtrlWord(CWF_VAL, i_protect, 0) || !PutCtrlWord(CWF_STR, i_pard) || // Reset para attributes
_CF._dwEffects & CFE_PROTECTED && !PutCtrlWord(CWF_STR, i_protect)) { goto CleanUp; }
if (_bTableLevel && !PutCtrlWord(CWF_STR, i_intbl) || _bTableLevel > 1 && !PutCtrlWord(CWF_VAL, i_itap, _bTableLevel) || PutBorders(FALSE)) { goto CleanUp; }
if(pPF->_wShadingStyle) { i = pPF->_wShadingStyle & 15; // Shading patterns
j = (pPF->_wShadingStyle >> 6) & 31; // Shading forecolor
k = pPF->_wShadingStyle >> 11; // Shading backcolor
if (i && i <= CSHADINGSTYLES && !PutCtrlWord(CWF_STR, rgiszShadingStyles[i - 1]) || j && !PutCtrlWord(CWF_VAL, i_cfpat, LookupColor(g_Colors[j-1]) + 1) || k && !PutCtrlWord(CWF_VAL, i_cbpat, LookupColor(g_Colors[k-1]) + 1)) { goto CleanUp; } } if(pPF->_wShadingWeight && !PutCtrlWord(CWF_VAL, i_shading, pPF->_wShadingWeight)) goto CleanUp;
// Paragraph numbering
_fBullet = _fBulletPending = FALSE; _nNumber = pPF->UpdateNumber(_nNumber, pPFPrev);
if(pPF->_wNumbering) // Write numbering info
{ LONG iFont = _symbolFont; WORD wStyle = pPF->_wNumberingStyle & 0xF00;
if(pPF->IsListNumbered()) { const CCharFormat *pCF; WCHAR szNumber[CCHMAXNUMTOSTR];
CTxtPtr rpTX(prtp->_rpTX); CFormatRunPtr rpCF(prtp->_rpCF);
rpCF.Move(rpTX.FindEOP(tomForward)); rpCF.AdjustBackward(); pCF = _ped->GetCharFormat(rpCF.GetFormat()); iFont = LookupFont(pCF); if(iFont < 0) { iFont = 0; TRACEERRORSZ("CWRTFW::WriteParaFormat: illegal bullet font"); } _nFont = iFont; // TODO: make the following smarter, i.e., may need to increment
// _nNumber instead of resetting it to 1.
_cpg = CodePageFromCharRep(pCF->_iCharRep);
i = 0; if(pPF->_wNumbering <= tomListNumberAsUCRoman) i = pPF->_wNumbering - tomListNumberAsArabic;
WCHAR ch = (wStyle == PFNS_PARENS || wStyle == PFNS_PAREN) ? ')' : (wStyle == PFNS_PERIOD) ? '.' : 0; if(wStyle != PFNS_NONUMBER) // Unless number suppressed
{ // write \pntext group
pPF->NumToStr(szNumber, _nNumber, fRtfWrite); if (!printF(szBeginNumberGroup, iFont) || WritePcData(szNumber, _cpg, FALSE) || !printF(szEndNumberGroup)) { goto CleanUp; } } j = pPF->_wNumberingStyle & 3; if (!printF(szBeginNumberFmt, wStyle == PFNS_NONUMBER ? "cont" : "body", iFont, pPF->_wNumberingTab, pPF->_wNumberingStart) || IN_RANGE(1, j, 2) && !PutCtrlWord(CWF_STR, j == 1 ? i_pnqc : i_pnqr) || !PutCtrlWord(CWF_STR, rgiszNumberStyle[i]) || wStyle == PFNS_PARENS && !printF(szpntxtb) || ch && !printF(szpntxta, ch) || !printF(szEndGroupCRLF)) { goto CleanUp; } } else { if (!printF(szBulletGroup, iFont) || !printF(szBulletFmt, wStyle == PFNS_NONUMBER ? "cont" : "blt", iFont, pPF->_wNumberingTab)) { goto CleanUp; } } _fBullet = TRUE; }
dwEffects = pPF->_wEffects & ((1 << CPFEFFECTS) - 1); if (_ped->IsBiDi() && !(dwEffects & PFE_RTLPARA) && !PutCtrlWord(CWF_STR, i_ltrpar)) //ltrpar attribute
{ goto CleanUp; }
for(c = CPFEFFECTS; dwEffects && c--; // Output PARAFORMAT2 effects
dwEffects >>= 1) { // rgiszPFEffects[] contains PF effect keywords in the
// order max PFE_xx to min PFE-xx
AssertSz(rgiszPFEffects[2] == i_hyphpar, "CRTFWrite::WriteParaFormat(): rgiszPFEffects is out-of-sync with PFE_XXX"); // \hyphpar has opposite logic to our PFE_DONOTHYPHEN so we emit
// \hyphpar0 to toggle the property off
if (dwEffects & 1 && !PutCtrlWord((c == 2) ? CWF_VAL : CWF_STR, rgiszPFEffects[c], 0)) { goto CleanUp; } } // Put out para indents. RTF first indent = -PF.dxOffset
// RTF left indent = PF.dxStartIndent + PF.dxOffset
if(IsHeadingStyle(pPF->_sStyle) && !PutCtrlWord(CWF_VAL, i_s, -pPF->_sStyle-1)) goto CleanUp; if (pPF->_dxOffset && !PutCtrlWord(CWF_VAL, i_fi, -pPF->_dxOffset) || pPF->_dxStartIndent + pPF->_dxOffset && !PutCtrlWord(CWF_VAL, (pPF->IsRtlPara()) ? i_ri : i_li, pPF->_dxStartIndent + pPF->_dxOffset) || pPF->_dxRightIndent && !PutCtrlWord(CWF_VAL, (pPF->IsRtlPara()) ? i_li : i_ri, pPF->_dxRightIndent) || pPF->_dySpaceBefore && !PutCtrlWord(CWF_VAL, i_sb, pPF->_dySpaceBefore) || pPF->_dySpaceAfter && !PutCtrlWord(CWF_VAL, i_sa, pPF->_dySpaceAfter)) { goto CleanUp; }
if(dwRule) // Special line spacing active
{ i = 0; // Default "At Least" or
if (dwRule == tomLineSpaceExactly) // "Exactly" line spacing
dy = -abs(dy); // Use negative for "Exactly"
else if(dwRule == tomLineSpaceMultiple) // RichEdit uses 20 units/line
{ // RTF uses 240 units/line
i++; dy *= 12; }
else if (dwRule != tomLineSpaceAtLeast && dy > 0) { i++; // Multiple line spacing
if (dwRule <= tomLineSpaceDouble) // 240 units per line
dy = 120 * (dwRule + 2); } if (!PutCtrlWord(CWF_VAL, i_sl, dy) || !PutCtrlWord(CWF_VAL, i_slmult, i)) { goto CleanUp; } }
if (IN_RANGE(PFA_RIGHT, pPF->_bAlignment, PFA_JUSTIFY) && !PutCtrlWord(CWF_STR, rgiszAlignment[pPF->_bAlignment - 1])) { goto CleanUp; }
prgxTabs = pPF->GetTabs(); for (i = 0; i < cTab; i++) { pPF->GetTab(i, &tabPos, &tabAlign, &tabLead, prgxTabs); AssertSz (tabAlign <= tomAlignBar && tabLead <= 5, "CRTFWrite::WriteParaFormat: illegal tab leader/alignment");
i_t = i_tb; // Default \tb (bar tab)
if (tabAlign != tomAlignBar) // It isn't a bar tab
{ i_t = i_tx; // Use \tx for tabPos
if (tabAlign && // Put nonleft alignment
!PutCtrlWord(CWF_STR, rgiszTabAlign[tabAlign-1])) { goto CleanUp; } } if (tabLead && // Put nonzero tab leader
!PutCtrlWord(CWF_STR, rgiszTabLead[tabLead-1]) || !PutCtrlWord(CWF_VAL, i_t, tabPos)) { goto CleanUp; } }
CleanUp: return _ecParseError; }
/*
* CRTFWrite::WriteText(cwch, lpcwstr, nCodePage, fIsDBCS, fQuadBackSlash) * * @mfunc * Write out <p cwch> chars from the Unicode text string <p lpcwstr> taking care to * escape any special chars. The Unicode text string is scanned for characters which * map directly to RTF strings, and the surrounding chunks of Unicode are written * by calling WriteTextChunk. * * @rdesc * EC The error code */ EC CRTFWrite::WriteText( LONG cwch, //@parm # chars in buffer
LPCWSTR lpcwstr, //@parm Pointer to text
INT nCodePage, //@parm Code page to use to convert to DBCS
BOOL fIsDBCS, //@parm If TRUE, lpcwstr is DBCS string stuffed into WSTR
BOOL fQuadBackSlash) //@parm If TRUE, write 4 \ for each \
{ TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteText");
WCHAR *pwchScan; WCHAR *pwchStart;
if(_fBulletPending) { _fBulletPending = FALSE; if(!_nNumber) { if(!printF(szBulletGroup, _symbolFont)) goto CleanUp; } else if(!_pPF->IsNumberSuppressed()) { WCHAR szNumber[CCHMAXNUMTOSTR]; _pPF->NumToStr(szNumber, ++_nNumber, fRtfWrite); if (!printF(szBeginNumberGroup, _nFont) || WritePcData(szNumber, _cpg, FALSE) || !printF(szEndNumberGroup)) { goto CleanUp; } } }
pwchScan = const_cast<LPWSTR>(lpcwstr); pwchStart = pwchScan; if(_CF._iCharRep == SYMBOL_INDEX) { pwchScan += cwch; cwch = 0; }
// Step through the Unicode buffer, weeding out characters that have
// known translations to RTF strings
while(cwch-- > 0) { WCHAR wch = *pwchScan;
// If this is a string for which the MultiByteToUnicode conversion
// failed, the buffer will be filled with ANSI bytes stuffed into
// wchar's (one per). In this case, we don't want to map trail bytes
// to RTF strings.
if(fIsDBCS && GetTrailBytesCount(wch, nCodePage) && nCodePage != CP_UTF8) { // If we have more characters in the buffer, then this is the
// DBC pair. Otherwise, treat it as single character.
if(cwch > 0) { cwch--; pwchScan += 2; continue; } }
// if the char is one for which there is an appropriate RTF string
// write the preceding chars and output the RTF string
if(!IN_RANGE(' ', wch, 'Z') && !IN_RANGE('a', wch, 'z') && !IN_RANGE(SOFTHYPHEN + 1, wch, ENSPACE - 1) && wch <= BULLET && MapsToRTFKeywordW(wch)) { if (pwchScan != pwchStart && WriteTextChunk(pwchScan - pwchStart, pwchStart, nCodePage, fIsDBCS, fQuadBackSlash)) { goto CleanUp; }
// Map the char(s) to the RTF string
int cwchUsed = MapToRTFKeyword(pwchScan, cwch, MAPTOKWD_UNICODE, fQuadBackSlash); if(_ecParseError != ecNoError) // Can happen if CELL encountered
goto CleanUp; // for _bTableLevel = 0
cwch -= cwchUsed; pwchScan += cwchUsed;
// Start of next run of unprocessed chars is one past current char
pwchStart = pwchScan + 1; } pwchScan++; }
// Write last chunk
if (pwchScan != pwchStart && WriteTextChunk(pwchScan - pwchStart, pwchStart, nCodePage, fIsDBCS, fQuadBackSlash)) { goto CleanUp; }
CleanUp: return _ecParseError; }
/*
* CRTFWrite::WriteTextChunk(cwch, lpcwstr, nCodePage, fIsDBCS, fQuadBackSlash) * * @mfunc * Write out <p cwch> chars from the Unicode text string <p lpcwstr> taking care to * escape any special chars. Unicode chars which cannot be converted to * DBCS chars using the supplied codepage, <p nCodePage>, are written using the * \u RTF tag. * * @rdesc * EC The error code */ EC CRTFWrite::WriteTextChunk( LONG cwch, //@parm # chars in buffer
LPCWSTR lpcwstr, //@parm Pointer to text
INT nCodePage, //@parm Code page to use to convert to DBCS
BOOL fIsDBCS, //@parm If TRUE, lpcwstr is DBCS string stuffed into WSTR
BOOL fQuadBackSlash) //@parm If TRUE, write 4 \ for each \
{ // FUTURE(BradO): There is alot of commonality b/t this routine and
// WritePcData. We should re-examine these routines and consider
// combining them into a common routine.
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteTextChunk");
BYTE b; LONG cbAnsi; UINT ch; BOOL fMissingCodePage = FALSE; BOOL fUsedDefault = FALSE;
// When WideCharToMultiByte fails to convert a char, the following default
// char is used as a placeholder in the string being converted
const char chToDBCSDefault = 0;
// Allocate temp buffer for ANSI text we convert to
LONG cbAnsiBufferSize = cachBufferMost * (nCodePage == CP_UTF8 ? 3 : MB_LEN_MAX); if (!_pbAnsiBuffer) { // If the code page was CP_UTF8, it will always be CP_UTF8 for this instance
_pbAnsiBuffer = (BYTE *)PvAlloc(cbAnsiBufferSize, GMEM_FIXED); if (!_pbAnsiBuffer) { _ped->GetCallMgr()->SetOutOfMemory(); _ecParseError = ecNoMemory; return ecNoMemory; } }
AssertSz(cwch <= cachBufferMost, "Too many chars for buffer");
// Convert Unicode (or fIsDBCS) buffer to ANSI
if(fIsDBCS) { // Supply some bogus code page which will force direct conversion
// from wchar to bytes (losing high byte of wchar).
// Also, don't want to use default char replacement in this case.
cbAnsi = WCTMB(INVALID_CODEPAGE, 0, lpcwstr, cwch, (char *)_pbAnsiBuffer, cbAnsiBufferSize, NULL, NULL, NULL); } else { cbAnsi = WCTMB(nCodePage, 0, lpcwstr, cwch, (char *)_pbAnsiBuffer, cbAnsiBufferSize, &chToDBCSDefault, &fUsedDefault, &fMissingCodePage); } Assert(cbAnsi > 0);
BYTE *pbAnsi = _pbAnsiBuffer; BOOL fMultiByte = (cbAnsi > cwch) || fIsDBCS || fMissingCodePage;
while (!_ecParseError && cbAnsi-- > 0) { b = *pbAnsi; ch = *lpcwstr;
// Compare ASCII chars to their Unicode counterparts to check
// that we're in sync
AssertSz(cwch <= 0 || ch > 127 || b == ch, "CRTFWrite::WriteText: Unicode and DBCS strings out of sync");
// If _fNCRForNonASCII, output the \uN tag for all nonASCII chars.
// This is useful because many Unicode chars that aren't in the
// target codepage are converted by WideCharToMultiByte() to some
// "best match char" for the codepage, e.g., alpha (0x3B1) converts
// to 'a' for cpg 1252.
//
// For NT 5, we use WC_NO_BEST_FIT_CHARS, which causes our regular
// algorithm to output \uN values whenever the system cannot convert
// a character correctly. This still requires readers that can handle
// multicodepage RTF, which is problematic for some RTF-to-HTML
// converters.
if(MapsToRTFKeywordA(b)) { int cchUsed = MapToRTFKeyword(pbAnsi, cbAnsi, MAPTOKWD_ANSI, fQuadBackSlash); cbAnsi -= cchUsed; pbAnsi += cchUsed; lpcwstr += cchUsed; cwch -= cchUsed; } else if(nCodePage == CP_UTF8) { PutChar(b); // Output 1st byte in any
if(b >= 0xC0) // case. At least 2-byte
{ // At least 2-byte lead
pbAnsi++; // byte, so output a
Assert(cbAnsi && IN_RANGE(0x80, *pbAnsi, 0xBF)); cbAnsi--; // trail byte
PutChar(*pbAnsi); if(b >= 0xE0) // 3-byte lead byte, so
{ // output another trail
pbAnsi++; // byte
Assert(cbAnsi && IN_RANGE(0x80, *pbAnsi, 0xBF)); cbAnsi--; PutChar(*pbAnsi); } } } else { LONG cbChar = fMultiByte && cbAnsi && GetTrailBytesCount(b, nCodePage) ? 2 : 1; if(ch >= 0x80 && !fIsDBCS && _fNCRForNonASCII && nCodePage != CP_SYMBOL) { // Output /uN for nonASCII
if(cbChar != _cbCharLast) { _cbCharLast = cbChar; // cb to follow /uN
if(!PutCtrlWord(CWF_VAL, i_uc, cbChar)) return _ecParseError; } if(!PutCtrlWord(CWF_SVAL, i_u, ch)) return _ecParseError; Assert(chToDBCSDefault != '?'); if(fUsedDefault) // Don't output another /uN
{ // below
b = '?'; _fNeedDelimeter = FALSE; } } if(cbChar == 2) { pbAnsi++; // Output DBCS pair
cbAnsi--; if(fIsDBCS) { lpcwstr++; cwch--; } printF(szEscape2CharFmt, b, *pbAnsi); } else { if(b == chToDBCSDefault && fUsedDefault) { // WideCharToMultiByte() couldn't complete a conversion so it
// used the default char we provided (0) used as a placeholder.
// In this case we want to output the original Unicode char.
if(!PutCtrlWord(CWF_SVAL, i_u, (cwch > 0 ? ch : TEXT('?')))) return _ecParseError;
_fNeedDelimeter = FALSE; if(!PutChar('?')) return _ecParseError; } else if(!IN_RANGE(32, b, 127)) printF(szEscapeCharFmt, b);
else PutChar(b); } } pbAnsi++; lpcwstr++; cwch--; } return _ecParseError; }
/*
* CRTFWrite::WriteInfo() * * @mfunc * Write out East Asia specific data. * * @rdesc * EC The error code */ EC CRTFWrite::WriteInfo() { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteInfo");
// TODO(BradO): Ultimately it would be nice to set some kind of
// fRTFFE bit to determine whether to write \info stuff. For now,
// we rely on the fact that lchars and fchars info actually exists
// to determine whether to write out the \info group.
if(_fRangeHasEOP && !printF(RTF_GENINFO)) return _ecParseError;
#ifdef UNDER_WORK
if (!(_dwFlags & fRTFFE) || // Start doc area
!PutCtrlWord(CWF_GRP, i_info) || !printF("{\\horzdoc}")) goto CleanUp;
// Write out punctuation character info
CHAR sz[PUNCT_MAX]; if(UsVGetPunct(_ped->lpPunctObj, PC_FOLLOWING, sz, sizeof(sz)) > PUNCT_MAX - 2) goto CleanUp;
if(!Puts("{\\*\\fchars") || WritePcData(sz) || !PutChar(chEndGroup)) goto CleanUp; if(UsVGetPunct(ped->lpPunctObj, PC_LEADING, sz, sizeof(sz)) > PUNCT_MAX+2) goto CleanUp;
if(!Puts("{\\*\\lchars") || WritePcData(sz) || !PutChar(chEndGroup)) goto CleanUp;
Puts(szEndGroupCRLF); // End info group
#endif
LPSTR lpstrLeading = NULL; LPSTR lpstrFollowing = NULL;
// If either succeeds (but evaluate both)
if(((_ped->GetLeadingPunct(&lpstrLeading) == NOERROR) + (_ped->GetFollowingPunct(&lpstrFollowing) == NOERROR)) && (lpstrLeading || lpstrFollowing)) { if (!PutCtrlWord(CWF_GRP, i_info) || !Puts(szHorzdocGroup, sizeof(szHorzdocGroup) - 1)) { goto CleanUp; } if (lpstrLeading && (!PutCtrlWord(CWF_AST, i_lchars) || !Puts(lpstrLeading, strlen(lpstrLeading)) || !PutChar(chEndGroup))) { goto CleanUp; } if (lpstrFollowing && (!PutCtrlWord(CWF_AST, i_fchars) || !Puts(lpstrFollowing, strlen(lpstrFollowing)) || !PutChar(chEndGroup))) { goto CleanUp; } Puts(szEndGroupCRLF, sizeof(szEndGroupCRLF) - 1); // End info group
}
CleanUp: return _ecParseError; }
/*
* CRTFWrite::WriteRtf() * * @mfunc * Write range _prg to output stream _pes. * * @rdesc * LONG Number of chars inserted into text; 0 means none were * inserted, OR an error occurred. */ LONG CRTFWrite::WriteRtf() { TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteRtf");
LONG cch, cchBuffer; LONG cchCF, cchPF; LONG cchT; LONG cpMin, cpMost; DWORD dwFlagsSave = _dwFlags; BOOL fBackground; BOOL fOutputEndGroup; LONG i, j; LONG lDocDefaultTab; WCHAR * pch; WCHAR * pchBuffer; CTxtEdit * ped = _ped; CDocInfo * pDocInfo = ped->GetDocInfo(); CRchTxtPtr rtp(*_prg); WORD wCodePage = CP_ACP;
AssertSz(_prg && _pes, "CRTFW::WriteRtf: improper initialization");
cch = _prg->GetRange(cpMin, cpMost); // Get rtp = cpMin, cch > 0
rtp.SetCp(cpMin);
_fRangeHasEOP = _prg->fHasEOP(); // Maintained for Selection
if(!_prg->IsSel()) // Validate range for RTF
{ // writing
CTxtPtr tp(rtp._rpTX);
_fRangeHasEOP = tp.IsAtEOP(); if(!_fRangeHasEOP && tp.FindEOP(cch, NULL)) _fRangeHasEOP = TRUE; }
// Allocate buffers for text we pick up and for RTF output
pchBuffer = (WCHAR *) PvAlloc(cachBufferMost * (sizeof(WCHAR) + 1) + 1, GMEM_FIXED); // Final 1 is for debug
if(!pchBuffer) { fOutputEndGroup = FALSE; goto RAMError; } _pchRTFBuffer = (CHAR *)(pchBuffer + cachBufferMost);
_pchRTFEnd = _pchRTFBuffer; // Initialize RTF buffer ptr
_cchBufferOut = 0; // and character count
_cchOut = 0; // and character output
// Determine the \ansicpgN value
if(!pDocInfo) { fOutputEndGroup = TRUE; goto RAMError; } fBackground = !(_dwFlags & SFF_SELECTION) && pDocInfo->_nFillType != -1;
while(rtp._rpTX.IsAtTRD(ENDFIELD)) // Bypass any row-end delimiters
cch -= rtp.AdvanceCRLF();
BOOL fNameIsDBCS; if(BuildTables(rtp, cch, fNameIsDBCS)) goto CleanUp;
wCodePage = (_dwFlags & SF_USECODEPAGE) ? HIWORD(_dwFlags) : pDocInfo->_wCpg;
if (fNameIsDBCS && wCodePage == CP_UTF8) { // Cannot have UTF8 if we have any run containing broken DBCS.
// Default back to regular rtf
wCodePage = pDocInfo->_wCpg; _dwFlags &= ~SF_USECODEPAGE; }
// Start RTF with \rtfN, \urtfN, or \pwdN group
i = (_dwFlags & SF_RTFVAL) >> 16; if (!PutCtrlWord(CWF_GRV, (wCodePage == CP_UTF8) ? i_urtf : (_dwFlags & SFF_PWD) ? i_pwd : i_rtf, i + 1) || ped->IsBiDi() && !Puts("\\fbidis", 7) || !PutCtrlWord(CWF_STR, i_ansi)) { goto CleanUpNoEndGroup; }
if (wCodePage != tomInvalidCpg && wCodePage != CP_ACP && !PutCtrlWord(CWF_VAL, i_ansicpg, wCodePage == CP_UTF8 ? pDocInfo->_wCpg : wCodePage)) { goto CleanUp; }
if(!printF(szDefaultFont)) goto CleanUp;
LCID lcid; LANGID langid;
if (ped->GetDefaultLCID(&lcid) == NOERROR && lcid != tomInvalidLCID && (langid = LANGIDFROMLCID(lcid)) && !PutCtrlWord(CWF_VAL, i_deflang, langid)) { goto CleanUp; }
if (ped->GetDefaultLCIDFE(&lcid) == NOERROR && lcid != tomInvalidLCID && (langid = LANGIDFROMLCID(lcid)) && !PutCtrlWord(CWF_VAL, i_deflangfe, langid)) { goto CleanUp; }
LONG lDocType; ped->GetDocumentType(&lDocType); if (lDocType && ped->IsBiDi() && !PutCtrlWord(CWF_STR, lDocType == DT_RTLDOC ? i_rtldoc : i_ltrdoc)) { goto CleanUp; }
lDocDefaultTab = pDocInfo->_dwDefaultTabStop; if(!lDocDefaultTab) lDocDefaultTab = lDefaultTab;
if (lDocDefaultTab != 720 && !PutCtrlWord(CWF_VAL, i_deftab, lDocDefaultTab) || WriteFontTable() || WriteColorTable()) { goto CleanUp; }
if(_nHeadingStyle) { if(!PutCtrlWord(CWF_GRP, i_stylesheet) || !printF(szNormalStyle)) goto CleanUp; for(i = 1; i < -_nHeadingStyle; i++) { if(!printF(szHeadingStyle, i, i)) goto CleanUp; } Puts(szEndGroupCRLF, sizeof(szEndGroupCRLF) - 1); // End font table group
} LRESULT lres; ped->GetViewKind(&lres); if(fBackground) lres = 5; // Word Online view
ped->GetViewScale(&j); if (WriteInfo() || _fRangeHasEOP && !PutCtrlWord(CWF_VAL, i_viewkind, lres) || (_dwFlags & SFF_PERSISTVIEWSCALE) && j != 100 && !PutCtrlWord(CWF_VAL, i_viewscale, j)) { goto CleanUp; }
// Write Unicode character byte count for use by entire document (since
// we don't use \plain's and since \ucN behaves as a char formatting tag,
// we're safe outputting it only once).
if(!PutCtrlWord(CWF_VAL, i_uc, iUnicodeCChDefault)) goto CleanUp;
// Write STextFlow for @font or textflow isn't tflowES
if (ped->_fUseAtFont || ped->_pdp->GetTflow() != tflowES) {
#if tflowES != 0 || tflowSW != 1 || tflowWN != 2 || tflowNE != 3
#error "Wrong tflow values assumed"
#endif
// ES SW WN NE
static BYTE rgbTextFlow[] = {0, 3, 8, 2, // Normal fonts
4, 1, 7, 6}; // @ fonts
j = ped->_pdp->GetTflow(); if(ped->_fUseAtFont) j += 4;
j = rgbTextFlow[j]; if (j == 1 && !PutCtrlWord(CWF_STR, i_vertdoc)) goto CleanUp;
if(!PutCtrlWord(CWF_VAL, i_stextflow, j)) goto CleanUp; }
if(fBackground && WriteBackgroundInfo(pDocInfo)) goto CleanUp;
while (cch > 0) { // Get next run of chars with same para formatting
if(WriteParaFormat(&rtp, &cch)) // Write paragraph formatting
goto CleanUp;
cchPF = rtp.GetCchLeftRunPF(); cchPF = min(cchPF, cch);
AssertSz(!cch || cchPF, "CRTFW::WriteRtf: Empty para format run!");
while (cchPF > 0) { // Get next run of characters with same char formatting
cchCF = rtp.GetCchLeftRunCF(); cchCF = min(cchCF, cchPF); AssertSz(cchCF, "CRTFW::WriteRtf: Empty char format run!");
const CCharFormat * pCF = rtp.GetCF(); INT nCodePage = CP_UTF8; if(!IsUTF8) { nCodePage = CodePageFromCharRep(pCF->_iCharRep); if(!nCodePage) nCodePage = 1252; if(nCodePage == CP_ACP && (_dwFlags & SF_USECODEPAGE)) nCodePage = HIWORD(_dwFlags); } LONG cchMove = WriteCharFormat(&rtp, cch, nCodePage);// Write char attributes
if(cchMove < 0) // Error
goto CleanUp;
cch -= cchMove; // In case writing friendly
cchPF -= cchMove; // URL, cch > 0
cchCF -= cchMove;
while (cchCF > 0) { cchBuffer = min(cachBufferMost, cchCF); // FUTURE: since this routine only reads the backing store
// and GetText only reads it, we can avoid allocating the
// buffer and use CTxtPtr::GetPch() directly as in
// CMeasurer::Measure()
cchBuffer = rtp._rpTX.GetText(cchBuffer, pchBuffer); pch = pchBuffer; cchT = cchBuffer; if(cchT > 0) { WCHAR * pchWork = pch; LONG cchWork = cchT; LONG cchTWork; UINT ch = 0; LONG cp = rtp.GetCp();
while (cchWork >0) { cchT = cchWork; pch = pchWork; while (cchWork > 0) // Search for objects
{ ch = *pchWork++; if(ch >= WCH_EMBEDDING) break; // Will write out object
cchWork--; // or ignore char
}
cchTWork = cchT - cchWork; if(cchTWork) // Write text before object
{ if(WriteText(cchTWork, pch, nCodePage, (pCF->_dwEffects & CFE_RUNISDBCS), FALSE)) { goto CleanUp; } } cp += cchTWork; if(cchWork > 0) // There is an object or ignorable
{ if(ch == WCH_EMBEDDING) { if(_fIncludeObjects) { COleObject *pobj;
Assert(ped->GetObjectMgr());
pobj = ped->GetObjectMgr()->GetObjectFromCp(cp); if(!pobj) goto CleanUp;
// First, commit the object to make sure the pres.
// caches, etc. are up-to-date. Don't worry
// about errors here.
pobj->SafeSaveObject(); WriteObject(cp, pobj); }
else if(!Puts(szObjPosHolder, sizeof(szObjPosHolder) - 1)) goto CleanUp; } cp++; cchWork--; } } } rtp.Move(cchBuffer); cchCF -= cchBuffer; cchPF -= cchBuffer; cch -= cchBuffer; } } } if(_dwFlags & SFF_WRITEXTRAPAR) PutPar();
CleanUp: if (_fFieldResult) { if (_nFieldFont != -1 && !PutCtrlWord(CWF_VAL, i_f, _nFieldFont)) return FALSE;
_fFieldResult = FALSE; if (!Puts("}}}", 3)) return FALSE; }
while(_bTableLevel > 0) // Finish off incomplete row
{ AssertSz(!_prg->IsSel(), "CRTFWrite::WriteRtf: invalid selection");
for(; _iCell < _cCell; _iCell++) PutCtrlWord(CWF_STR, _bTableLevel > 1 ? i_nestcell : i_cell); PutCtrlWord(CWF_STR, _bTableLevel > 1 ? i_nestrow : i_row); _bTableLevel--; _iCell = rgiCell[_bTableLevel]; _cCell = rgcCell[_bTableLevel]; }
// End RTF group
Puts(szEndGroupCRLF, sizeof(szEndGroupCRLF)); FlushBuffer();
CleanUpNoEndGroup: FreePv(pchBuffer);
if(_ecParseError != ecNoError || dwFlagsSave != _dwFlags) { if(_ecParseError == ecNoError) _ecParseError = ecUTF8NotUsed;
TRACEERRSZSC("CRTFW::WriteRtf()", _ecParseError); Tracef(TRCSEVERR, "Writing error: %s", rgszParseError[_ecParseError]); if(!_pes->dwError || ped->Get10Mode()) // Make error code OLE-like
_pes->dwError = -abs(_ecParseError); _cchOut = 0; } return _cchOut;
RAMError: ped->GetCallMgr()->SetOutOfMemory(); _ecParseError = ecNoMemory;
if(fOutputEndGroup) goto CleanUp;
goto CleanUpNoEndGroup; }
|