windows-nt/Source/XPSP1/NT/windows/richedit/re30/rtfwrit.cpp

2556 lines
66 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*
* @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-1998, Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_rtfwrit.h"
#include "_objmgr.h"
#include "_coleobj.h"
#include "_font.h"
ASSERTDATA
extern const KEYWORD rgKeyword[];
//========================= Global String Constants ==================================
BYTE bCharSetANSI = ANSI_CHARSET; // 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
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[] =
{
"\\%s", "\\%s%d", "{\\%s", "{\\*\\%s", "{\\%s%d"
};
// 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[] = ";}";
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 szEndRow[] = "\\row\r\n";
#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
};
// 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 BYTE 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_disabled, 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 BYTE 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
};
#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 BYTE rgiszBorders[] = // Border combination keywords
{
i_box,
i_brdrt, i_brdrl, i_brdrb, i_brdrr,
i_trbrdrt, i_trbrdrl, i_trbrdrb, i_trbrdrr,
i_clbrdrt, i_clbrdrl, i_clbrdrb, i_clbrdrr
};
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
};
/*
* 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
*/
inline BOOL CRTFWrite::MapsToRTFKeywordW(
WCHAR wch)
{
return
!_fNCRForNonASCII &&
(IN_RANGE(TAB, wch, CR) ||
wch == CELL ||
wch == BSLASH ||
wch == LBRACE ||
wch == RBRACE ||
IN_RANGE(ENSPACE, wch, EMSPACE) ||
IN_RANGE(ENDASH, wch, EMDASH) ||
IN_RANGE(LQUOTE, wch, RQUOTE) ||
IN_RANGE(LDBLQUOTE, wch, RDBLQUOTE) ||
wch == BULLET ||
wch == chOptionalHyphen ||
wch == chNonBreakingSpace);
}
/*
* CRTFWrite::MapsToRTFKeywordA(ch)
*
* @mfunc
* Returns a flag indicating whether the character maps to an RTF keyword
*
* @rdesc
* BOOL TRUE if char maps to RTF keyword
*/
inline BOOL CRTFWrite::MapsToRTFKeywordA(char ch)
{
return IN_RANGE(TAB, ch, CR) ||
ch == CELL ||
ch == BSLASH ||
ch == LBRACE ||
ch == RBRACE;
}
/*
* CRTFWrite::MapToRTFKeywordW(pv, cch, iCharEncoding)
*
* @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,
int iCharEncoding)
{
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 LQUOTE:
case RDBLQUOTE:
case RQUOTE:
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 TAB:
#if TAB == CELL // If TABs and CELLs are equal, in
if(_pPF->InTable()) // tables convert TABs to CELL value
ch -= 2; // of 7 for rgiszTerminators[]
#else
case CELL:
#endif
case FF:
case VT:
PutCtrlWord(CWF_STR, rgiszTerminators[ch - (TAB - 2)]);
break;
case CR:
{
WCHAR ch1;
WCHAR ch2;
if(iCharEncoding == MAPTOKWD_ANSI)
{
char *pch = (char *)pv;
ch1 = pch[1];
ch2 = pch[2];
}
else
{
WCHAR *pch = (WCHAR *)pv;
ch1 = pch[1];
ch2 = pch[2];
}
if(cch > 1 && ch1 == CR && ch2 == LF)
{
// Translate CRCRLF to a blank (represents soft line break)
PutChar(' ');
cchRet = 2;
break;
}
if(cch && ch1 == LF) // Ignore LF after CR
{
cchRet = 1;
}
if(_pPF->InTable()) // CR terminates a row in our simple
{ // table model, so output \row
Puts(szEndRow, sizeof(szEndRow) - 1);
_fCheckInTable = TRUE;
break;
}
} // Fall thru to LF (EOP) case
case LF:
if (_ped->Get10Mode())
Puts(szPar10, sizeof(szPar10) - 1);
else
Puts(szPar, sizeof(szPar) - 1);
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 chOptionalHyphen:
ch = '-'; // Fall thru to printFLiteral
printFLiteral:
case BSLASH:
case LBRACE:
case RBRACE:
printF(szLiteralCharFmt, ch);
break;
case chNonBreakingSpace:
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;
if(!_ctfi)
ReadFontSubInfo();
#if defined(DEBUG) && !defined(MACPORT)
_hfileCapture = NULL;
#if !defined(PEGASUS)
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(PEGASUS)
#endif // defined(DEBUG) && !defined(MACPORT)
}
//======================== 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
_fCheckInTable = FALSE;
_pPF = NULL;
_pbAnsiBuffer = NULL;
}
/*
* 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(MACPORT) && !defined(PEGASUS)
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");
CheckDelimeter(); // 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::CheckInTable(fPutIntbl)
*
* @mfunc
* If _fCheckInTable or !fPutIntbl, output row header RTF. If fPutIntbl
* and _fCheckInTable, output \intbl as well. Note that fPutIntbl is
* FALSE when a PF is being output, since this control word needs to
* be output after the \pard, but the other row RTF needs to be output
* before the \pard.
*
* @rdesc
* BOOL TRUE if in table and outputted all relevant \trowd stuff
*/
BOOL CRTFWrite::CheckInTable(
BOOL fPutIntbl) //@parm TRUE if \intbl should be output
{
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::CheckInTable");
_fCheckInTable = FALSE;
if(_pPF->InTable())
{
if(!_fRangeHasEOP)
return TRUE;
LONG cTab = _pPF->_bTabCount;
LONG h = _pPF->_dxOffset;
LONG i, j = _pPF->_dxStartIndent;
LONG k = _pPF->_bAlignment;
DWORD Tab, Widths;
if (!PutCtrlWord(CWF_STR, i_trowd) || // Reset table properties
h && !PutCtrlWord(CWF_VAL, i_trgaph, h) ||
j && !PutCtrlWord(CWF_VAL, i_trleft, j) ||
IN_RANGE(PFA_RIGHT, k, PFA_CENTER) &&
!PutCtrlWord(CWF_STR, k == PFA_RIGHT ? i_trqr : i_trqc))
{
return FALSE;
}
PutBorders(TRUE);
const LONG *prgxTabs = _pPF->GetTabs();
for(i = 0; i < cTab; i++)
{
Tab = *prgxTabs++;
Widths = Tab >> 24;
if(Widths)
{
for(j = 0; j < 4; j++, Widths >>= 2)
{
LONG w = Widths & 3;
if(w && (!PutCtrlWord(CWF_STR, rgiszBorders[j + 9]) ||
!PutCtrlWord(CWF_VAL, i_brdrw, 15*w) ||
!PutCtrlWord(CWF_STR, i_brdrs)))
{
return FALSE;
}
}
CheckDelimeter();
}
if(!PutCtrlWord(CWF_VAL, i_cellx, GetTabPos(Tab)))
return FALSE;
}
if(!fPutIntbl || PutCtrlWord(CWF_STR, i_intbl))
return TRUE;
}
return FALSE;
}
/*
* 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, 10*(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;
}
}
CheckDelimeter(); // 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;
CheckDelimeter(); // 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(MACPORT) && !defined(PEGASUS)
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;
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 TCHAR * 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(' ')
CheckDelimeter();
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::LookupColor(colorref)
*
* @mfunc
* Return color-table index for color referred to by <p colorref>.
* If a match isn't found, an entry is added.
*
* @rdesc
* LONG Index into colortable
* <lt> 0 on error
*/
LONG CRTFWrite::LookupColor(
COLORREF colorref) //@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) == colorref)
return iclrf;
pclrf = _colors.Add(1, NULL); // If we couldn't find it,
if(!pclrf) // add it to color table
return -1;
*pclrf = colorref;
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->bCharSet == pCF->_bCharSet && // char set, 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->bCharSet = pCF->_bCharSet;
ptf->sCodePage = (short)GetCodePage (ptf->bCharSet);
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(rpCF, rpPF, cch, fNameIsDBCS)
*
* @mfunc
* Build font and color tables for write range of length <p cch>
*
* @rdesc
* EC The error code
*/
EC CRTFWrite::BuildTables(
CFormatRunPtr& rpCF, //@parm CF run ptr for start of write range
CFormatRunPtr& rpPF, //@parm PF run ptr for start of write range
LONG cch, //@parm # chars in write range
BOOL& fNameIsDBCS) //@parm OUT =TRUE if there is any CFE_FACENAMEISDBCS Run in selection.
{
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::BuildTables");
LONG i;
LONG ifmt = 0;
const CCharFormat * pCF = NULL;
const CParaFormat * pPF = NULL;
CFormatRunPtr rp(rpCF);
CFormatRunPtr rpPFtemp(rpPF);
LONG cchTotal = cch;
fNameIsDBCS = FALSE;
while(cch > 0)
{
ifmt = rp.GetFormat(); // _iFormat for next CF run
pCF = _ped->GetCharFormat(ifmt);
Assert(pCF);
if (pCF->_dwEffects & CFE_FACENAMEISDBCS)
fNameIsDBCS = TRUE;
// 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 ||
(!(pCF->_dwEffects & CFE_AUTOCOLOR) &&
LookupColor(pCF->_crTextColor) < 0) ||
(!(pCF->_dwEffects & CFE_AUTOBACKCOLOR) &&
LookupColor(pCF->_crBackColor) < 0))
{
break;
}
if(!rp.IsValid())
break;
cch -= rp.GetCchLeft();
rp.NextRun();
}
// now look for bullets; if found, then we need to include
// the "Symbol" font
cch = cchTotal;
_symbolFont = 0;
while( cch > 0 )
{
ifmt = rpPFtemp.GetFormat();
pPF = _ped->GetParaFormat(ifmt);
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._bCharSet = SYMBOL_CHARSET;
CF._bPitchAndFamily = FF_DONTCARE;
// Save Font index for Symbol. Reset it to 0 if LookupFont
// returns error.
_symbolFont = LookupFont(&CF);
_symbolFont = max(_symbolFont, 0);
// We don't need to bother looking for more bullets, since
// in RichEdit 2.0, all bullets either have the same font or
// have their formatting information in the character format
// for the EOP mark.
break;
}
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(!rpPFtemp.IsValid())
break;
cch -= rpPFtemp.GetCchLeft();
rpPFtemp.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 TCHAR * szName;
TCHAR * 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->bCharSet ||
!FindTaggedFont(szName, ptf->bCharSet, &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->bCharSet)
ptf->sCodePage = (short)GetCodePage(ptf->bCharSet);
// Write charset. Win32 uses ANSI_CHARSET to mean the default Windows
// character set, so find out what it really is
extern BYTE bCharSetANSI;
if(ptf->bCharSet != DEFAULT_CHARSET)
{
BYTE bCharSet = ptf->bCharSet;
BOOL fWroteCharSet = TRUE;
if(ptf->bCharSet == PC437_CHARSET || IsPrivateCharSet(bCharSet))
{
fWroteCharSet = FALSE;
bCharSet = ANSI_CHARSET;
}
if(!PutCtrlWord(CWF_VAL, i_fcharset, bCharSet))
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(pCF)
*
* @mfunc
* Write deltas between CCharFormat <p pCF> and the previous CCharFormat
* given by _CF, and then set _CF = *<p pCF>.
*
* @rdesc
* EC The error code
*
* @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).
*/
EC CRTFWrite::WriteCharFormat(
const CCharFormat * pCF) //@parm Ptr to CCharFormat
{
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteCharFormat");
DWORD dwEffects = pCF->_dwEffects; // Current effects
DWORD dwChanges = _CF._dwEffects; // Previous effects (will be
LONG i; // changed between them)
LONG iFormat;
LONG iValue; // Control-word value
LONG i_sz; // Temp ctrl string index
LONG yOffset = pCF->_yOffset;
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
dwChanges ^= dwEffects; // Now dwChanges is the
// diff between effects
if (dwChanges & CFE_AUTOCOLOR || // Change in autocolor
pCF->_crTextColor != _CF._crTextColor) // or text color
{
iValue = 0; // Default autocolor
if(!(dwEffects & CFE_AUTOCOLOR)) // Make that text color
iValue = LookupColor(pCF->_crTextColor) + 1;
if(!PutCtrlWord(CWF_VAL, i_cf, iValue))
goto CleanUp;
}
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))
goto CleanUp;
}
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) )
{
goto CleanUp;
}
// 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]))
goto CleanUp;
}
// 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))
goto CleanUp;
}
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))
goto CleanUp;
}
}
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))
goto CleanUp;
}
if (pCF->_bPitchAndFamily != _CF._bPitchAndFamily || // Change in font
pCF->_bCharSet != _CF._bCharSet ||
pCF->_iFont != _CF._iFont)
{
iValue = LookupFont(pCF);
if(iValue < 0 || !PutCtrlWord(CWF_VAL, i_f, iValue))
goto CleanUp;
// RichEdit encodes the current direction in bCharSet, but Word likes
// to know explicitly, so output the appropriate choice of \rtlch or
// \ltrch if the direction changes
BOOL fRTLCharSet = IsRTLCharSet(pCF->_bCharSet);
if (fRTLCharSet != IsRTLCharSet(_CF._bCharSet) &&
!PutCtrlWord(CWF_STR, fRTLCharSet ? i_rtlch : i_ltrch))
{
goto CleanUp;
}
}
if (pCF->_yHeight != _CF._yHeight) // Change in font size
{
iValue = (pCF->_yHeight + (pCF->_yHeight > 0 ? 5 : -5))/10;
if(!PutCtrlWord(CWF_VAL, i_fs, iValue))
goto CleanUp;
}
_CF = *pCF; // Update previous CCharFormat
CleanUp:
return _ecParseError;
}
/*
* CRTFWrite::WriteParaFormat(prtp)
*
* @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(
const CRchTxtPtr * prtp) //@parm Ptr to rich-text ptr at current cp
{
TRACEBEGIN(TRCSUBSYSRTFW, TRCSCOPEINTERN, "CRTFWrite::WriteParaFormat");
Assert(_ped);
//if(!_fRangeHasEOP) // Don't write para info if
// return _ecParseError; // range has no EOPs
const CParaFormat * pPFPrev = _pPF;
const CParaFormat * pPF = _pPF = prtp->GetPF();
BOOL fInTable = pPF->InTable();
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;
CheckInTable(FALSE);
if(fInTable)
cTab = 0; // Suppress \tab output
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(fInTable)
{
if(_fRangeHasEOP && !PutCtrlWord(CWF_STR, i_intbl))
goto CleanUp;
}
else if(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.AdvanceCp(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 = GetCodePage(pCF->_bCharSet);
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(!fInTable &&
(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 (!fInTable && 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)
*
* @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 a WSTR
{
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;
}
}
}
if(_fCheckInTable)
{
CheckInTable(TRUE);
if(_ecParseError)
goto CleanUp;
}
pwchScan = const_cast<LPWSTR>(lpcwstr);
pwchStart = pwchScan;
if(_CF._bCharSet == SYMBOL_CHARSET)
{
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(chOptionalHyphen + 1, wch, ENSPACE - 1) &&
wch <= BULLET &&
MapsToRTFKeywordW(wch))
{
if (pwchScan != pwchStart &&
WriteTextChunk(pwchScan - pwchStart, pwchStart, nCodePage,
fIsDBCS))
{
goto CleanUp;
}
// map the char(s) to the RTF string
int cwchUsed = MapToRTFKeyword(pwchScan, cwch, MAPTOKWD_UNICODE);
cwch -= cwchUsed;
pwchScan += cwchUsed;
// start of next run of unprocessed chars is one past current char
pwchStart = pwchScan + 1;
if(cwch && _fCheckInTable)
{
_fCheckInTable = FALSE;
if(!PutCtrlWord(CWF_STR, i_intbl))
goto CleanUp;
}
}
pwchScan++;
}
// write the last chunk
if (pwchScan != pwchStart &&
WriteTextChunk(pwchScan - pwchStart, pwchStart, nCodePage, fIsDBCS))
{
goto CleanUp;
}
CleanUp:
return _ecParseError;
}
/*
* CRTFWrite::WriteTextChunk(cwch, lpcwstr, nCodePage, fIsDBCS)
*
* @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 indicates whether lpcwstr is a Unicode string
// or a DBCS string stuffed into a WSTR
{
// 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;
LONG cbAnsiBufferSize;
LONG cbChar;
UINT ch;
BOOL fMissingCodePage = FALSE;
BOOL fMultiByte;
BOOL fUsedDefault = FALSE;
BYTE * pbAnsi;
BYTE * pbAnsiBuffer;
// 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
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)
goto RAMError;
}
pbAnsiBuffer = _pbAnsiBuffer;
// 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);
pbAnsi = pbAnsiBuffer;
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(!IN_RANGE(' ', b, 'z') && MapsToRTFKeywordA(b))
{
int cchUsed = MapToRTFKeyword(pbAnsi, cbAnsi, MAPTOKWD_ANSI);
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
{
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))
goto CleanUp;
}
if(!PutCtrlWord(CWF_VAL, i_u, ch))
goto CleanUp;
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_VAL, i_u, (cwch > 0 ? ch : TEXT('?'))))
goto CleanUp;
_fNeedDelimeter = FALSE;
if(!PutChar('?'))
goto CleanUp;
}
else if(!IN_RANGE(32, b, 127))
printF(szEscapeCharFmt, b);
else
PutChar(b);
}
}
pbAnsi++;
lpcwstr++;
cwch--;
}
goto CleanUp;
RAMError:
_ped->GetCallMgr()->SetOutOfMemory();
_ecParseError = ecNoMemory;
CleanUp:
return _ecParseError;
}
/*
* CRTFWrite::WriteInfo()
*
* @mfunc
* Write out Far East 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.
#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;
BOOL fOutputEndGroup;
LONG i, j;
LONG lDocDefaultTab;
TCHAR * pch;
TCHAR * 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. Don't write
CPFRunPtr rp(rtp); // partial table row unless
CTxtPtr tp(rtp._rpTX); // no CELLs are included
LONG Results;
_fRangeHasEOP = tp.IsAtEOP();
if(tp.FindEOP(cch, &Results)) // Need to get CELL info too
_fRangeHasEOP = TRUE;
if(rtp.InTable())
{
tp.SetCp(cpMin);
if(!_fRangeHasEOP) // Partial row
{
if(Results & FEOP_CELL) // Has 1 or more CELLs
{
while(tp.GetCp() < cpMost) // Stop at first CELL
{
if(tp.GetChar() == CELL)
{
cch = tp.GetCp() - cpMin;
break;
}
tp.AdvanceCp(1);
}
}
}
else if(!tp.IsAfterEOP())
{ // Move back to start of row
rtp.Advance(tp.FindEOP(tomBackward));
cch += cpMin - rtp.GetCp();
}
}
if(_fRangeHasEOP) // Check cpMost for table
{
rp.AdvanceCp(cpMost - cpMin); // Go to cpMost
if(rp.InTable())
{
tp.SetCp(cpMost);
cch += tp.FindEOP(tomForward); // Include whole row
}
}
}
// Allocate buffers for text we pick up and for RTF output
pchBuffer = (TCHAR *) PvAlloc(cachBufferMost * (sizeof(TCHAR) + 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;
}
BOOL fNameIsDBCS;
if (BuildTables(rtp._rpCF, rtp._rpPF, 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);
_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;
while (cch > 0)
{
// Get next run of chars with same para formatting
cchPF = rtp.GetCchLeftRunPF();
cchPF = min(cchPF, cch);
AssertSz(cchPF, "CRTFW::WriteRtf: Empty para format run!");
if(WriteParaFormat(&rtp)) // Write paragraph formatting
goto CleanUp;
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();
if (WriteCharFormat(pCF)) // Write char attributes
goto CleanUp;
INT nCodePage = CP_UTF8;
if(!IsUTF8)
{
if(IsPrivateCharSet(pCF->_bCharSet))
nCodePage = 1252; // Force \uN's
else
{
nCodePage = GetCodePage(pCF->_bCharSet);
if(nCodePage == CP_ACP && (_dwFlags & SF_USECODEPAGE))
nCodePage = HIWORD(_dwFlags);
}
}
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)
{
TCHAR * pchWork = pch;
LONG cchWork = cchT;
LONG cchTWork;
LONG cp = rtp.GetCp();
while (cchWork >0)
{
cchT = cchWork ;
pch = pchWork;
while (cchWork > 0 ) // search for objects
{
if(*pchWork++ == WCH_EMBEDDING)
break; // Will write out object
cchWork--;
}
cchTWork = cchT - cchWork;
if(cchTWork) // write text before object
{
if(WriteText(cchTWork, pch, nCodePage,
(pCF->_dwEffects & CFE_RUNISDBCS)))
{
goto CleanUp;
}
}
cp += cchTWork;
if(cchWork > 0) // there is an object
{
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();
if(_fIncludeObjects)
WriteObject(cp, pobj);
else if(!Puts(szObjPosHolder, sizeof(szObjPosHolder) - 1))
goto CleanUp;
cp++;
cchWork--;
}
}
}
rtp.Advance(cchBuffer);
cchCF -= cchBuffer;
cchPF -= cchBuffer;
cch -= cchBuffer;
}
}
}
CleanUp:
// End RTF group
Puts(szEndGroupCRLF, sizeof(szEndGroupCRLF));
FlushBuffer();
CleanUpNoEndGroup:
FreePv(pchBuffer);
if (_ecParseError != ecNoError)
{
TRACEERRSZSC("CRTFW::WriteRtf()", _ecParseError);
Tracef(TRCSEVERR, "Writing error: %s", rgszParseError[_ecParseError]);
if(!_pes->dwError) // 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;
}