mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4517 lines
123 KiB
4517 lines
123 KiB
/*
|
|
* @doc INTERNAL
|
|
*
|
|
* @module RTFREAD.CPP - RichEdit RTF reader (w/o objects) |
|
|
*
|
|
* This file contains the nonobject code of RichEdit RTF reader.
|
|
* See rtfread2.cpp for embedded-object code.
|
|
*
|
|
* Authors:<nl>
|
|
* Original RichEdit 1.0 RTF converter: Anthony Francisco <nl>
|
|
* Conversion to C++ and RichEdit 2.0 w/o objects: Murray Sargent
|
|
* Lots of enhancements/maintenance: Brad Olenick
|
|
*
|
|
* @devnote
|
|
* sz's in the RTF*.cpp and RTF*.h files usually refer to a LPSTRs,
|
|
* not LPWSTRs.
|
|
*
|
|
* @todo
|
|
* 1. Unrecognized RTF. Also some recognized won't round trip <nl>
|
|
* 2. In font.c, add overstrike for CFE_DELETED and underscore for
|
|
* CFE_REVISED. Would also be good to change color for CF.bRevAuthor
|
|
*
|
|
* Copyright (c) 1995-2002, Microsoft Corporation. All rights reserved.
|
|
*/
|
|
|
|
#include "_common.h"
|
|
#include "_rtfread.h"
|
|
#include "_util.h"
|
|
#include "_font.h"
|
|
#include "_disp.h"
|
|
|
|
ASSERTDATA
|
|
|
|
/*
|
|
* Global Variables
|
|
*/
|
|
|
|
#define PFM_ALLRTF (PFM_ALL2 | PFM_COLLAPSED | PFM_OUTLINELEVEL | PFM_BOX)
|
|
|
|
// for object attachment placeholder list
|
|
#define cobPosInitial 8
|
|
#define cobPosChunk 8
|
|
|
|
#if CFE_SMALLCAPS != 0x40 || CFE_ALLCAPS != 0x80 || CFE_HIDDEN != 0x100 \
|
|
|| CFE_OUTLINE != 0x200 || CFE_SHADOW != 0x400
|
|
#error "Need to change RTF char effect conversion routines
|
|
#endif
|
|
|
|
// for RTF tag coverage testing
|
|
#if defined(DEBUG) && !defined(NOFULLDEBUG)
|
|
#define TESTPARSERCOVERAGE() \
|
|
{ \
|
|
if(GetProfileIntA("RICHEDIT DEBUG", "RTFCOVERAGE", 0)) \
|
|
{ \
|
|
TestParserCoverage(); \
|
|
} \
|
|
}
|
|
#define PARSERCOVERAGE_CASE() \
|
|
{ \
|
|
if(_fTestingParserCoverage) \
|
|
{ \
|
|
return ecNoError; \
|
|
} \
|
|
}
|
|
#define PARSERCOVERAGE_DEFAULT() \
|
|
{ \
|
|
if(_fTestingParserCoverage) \
|
|
{ \
|
|
return ecStackOverflow; /* some bogus error */ \
|
|
} \
|
|
}
|
|
#else
|
|
#define TESTPARSERCOVERAGE()
|
|
#define PARSERCOVERAGE_CASE()
|
|
#define PARSERCOVERAGE_DEFAULT()
|
|
#endif
|
|
|
|
static WCHAR szRowEnd[] = {ENDFIELD, CR, 0};
|
|
static WCHAR szRowStart[] = {STARTFIELD, CR, 0};
|
|
WCHAR pchSeparateField[] = {SEPARATOR, 'F'};
|
|
WCHAR pchStartField[] = {STARTFIELD, 'F'};
|
|
|
|
// FF's should not have paragraph number prepended to them
|
|
inline BOOL CharGetsNumbering(WORD ch) { return ch != FF; }
|
|
|
|
// V-GUYB: PWord Converter requires loss notification.
|
|
#ifdef REPORT_LOSSAGE
|
|
typedef struct
|
|
{
|
|
IStream *pstm;
|
|
BOOL bFirstCallback;
|
|
LPVOID *ppwdPWData;
|
|
BOOL bLoss;
|
|
} LOST_COOKIE;
|
|
#endif
|
|
|
|
|
|
//======================== OLESTREAM functions =======================================
|
|
|
|
DWORD CALLBACK RTFGetFromStream (
|
|
RTFREADOLESTREAM *OLEStream, //@parm OleStream
|
|
void FAR * pvBuffer, //@parm Buffer to read
|
|
DWORD cb) //@parm Bytes to read
|
|
{
|
|
return OLEStream->Reader->ReadData ((BYTE *)pvBuffer, cb);
|
|
}
|
|
|
|
DWORD CALLBACK RTFGetBinaryDataFromStream (
|
|
RTFREADOLESTREAM *OLEStream, //@parm OleStream
|
|
void FAR * pvBuffer, //@parm Buffer to read
|
|
DWORD cb) //@parm Bytes to read
|
|
{
|
|
return OLEStream->Reader->ReadBinaryData ((BYTE *)pvBuffer, cb);
|
|
}
|
|
|
|
|
|
//============================ STATE Structure =================================
|
|
/*
|
|
* STATE::AddPF(PF, lDocType, dwMask, dwMask2)
|
|
*
|
|
* @mfunc
|
|
* If the PF contains new info, this info is applied to the PF for the
|
|
* state. If this state was sharing a PF with a previous state, a new
|
|
* PF is created for the state, and the new info is applied to it.
|
|
*
|
|
* @rdesc
|
|
* TRUE unless needed new PF and couldn't allocate it
|
|
*/
|
|
BOOL STATE::AddPF(
|
|
const CParaFormat &PF, //@parm Current RTFRead _PF
|
|
LONG lDocType, //@parm Default doc type to use if no prev state
|
|
DWORD dwMask, //@parm Mask to use
|
|
DWORD dwMask2) //@parm Mask to use
|
|
{
|
|
// Create a new PF if:
|
|
// 1. The state doesn't have one yet
|
|
// 2. The state has one, but it is shared by the previous state and
|
|
// there are PF deltas to apply to the state's PF
|
|
if(!pPF || dwMask && pstatePrev && pPF == pstatePrev->pPF)
|
|
{
|
|
Assert(!pstatePrev || pPF);
|
|
|
|
pPF = new CParaFormat;
|
|
if(!pPF)
|
|
return FALSE;
|
|
|
|
// Give the new PF some initial values - either from the previous
|
|
// state's PF or by CParaFormat initialization
|
|
if(pstatePrev)
|
|
{
|
|
// Copy the PF from the previous state
|
|
*pPF = *pstatePrev->pPF;
|
|
dwMaskPF = pstatePrev->dwMaskPF;
|
|
}
|
|
else
|
|
{
|
|
// We've just created a new PF for the state - there is no
|
|
// previous state to copy from. Use default values.
|
|
pPF->InitDefault(lDocType == DT_RTLDOC ? PFE_RTLPARA : 0);
|
|
dwMaskPF = PFM_ALLRTF;
|
|
dwMaskPF2 = PFM2_TABLEROWSHIFTED;
|
|
}
|
|
}
|
|
|
|
// Apply the new PF deltas to the state's PF
|
|
if(dwMask)
|
|
{
|
|
if(dwMask & PFM_TABSTOPS) // Don't change _iTabs here
|
|
{
|
|
pPF->_bTabCount = PF._bTabCount;
|
|
dwMask &= ~PFM_TABSTOPS;
|
|
}
|
|
pPF->Apply(&PF, dwMask, dwMaskPF2);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* STATE::DeletePF()
|
|
*
|
|
* @mfunc
|
|
* If the state's PF is not shared by the previous state, the PF for this
|
|
* state is deleted.
|
|
*/
|
|
void STATE::DeletePF()
|
|
{
|
|
if(pPF && (!pstatePrev || pPF != pstatePrev->pPF))
|
|
delete pPF;
|
|
|
|
pPF = NULL;
|
|
}
|
|
|
|
/*
|
|
* STATE::SetCodePage(CodePage)
|
|
*
|
|
* @mfunc
|
|
* If current nCodePage is CP_UTF8, use it for all conversions (yes, even
|
|
* for SYMBOL_CHARSET). Else use CodePage.
|
|
*/
|
|
void STATE::SetCodePage(
|
|
LONG CodePage)
|
|
{
|
|
if(nCodePage != CP_UTF8)
|
|
nCodePage = CodePage;
|
|
}
|
|
|
|
//============================ CRTFRead Class ==================================
|
|
/*
|
|
* CRTFRead::CRTFRead(prg, pes, dwFlags)
|
|
*
|
|
* @mfunc
|
|
* Constructor for RTF reader
|
|
*/
|
|
CRTFRead::CRTFRead (
|
|
CTxtRange * prg, //@parm CTxtRange to read into
|
|
EDITSTREAM * pes, //@parm Edit stream to read from
|
|
DWORD dwFlags //@parm Read flags
|
|
)
|
|
: CRTFConverter(prg, pes, dwFlags, TRUE)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::CRTFRead");
|
|
|
|
Assert(prg->GetCch() == 0);
|
|
|
|
//TODO(BradO): We should examine the member data in the constructor
|
|
// and determine which data we want initialized on construction and
|
|
// which at the beginning of every read (in CRTFRead::ReadRtf()).
|
|
|
|
_sDefaultFont = -1; // No \deff n control word yet
|
|
_sDefaultLanguage = INVALID_LANGUAGE;
|
|
_sDefaultLanguageFE = INVALID_LANGUAGE;
|
|
_sDefaultTabWidth = 0;
|
|
_sDefaultBiDiFont = -1;
|
|
_dwMaskCF = 0; // No char format changes yet
|
|
_dwMaskCF2 = 0;
|
|
_dwMaskPF = 0; // No char format changes yet
|
|
_dwMaskPF2 = 0; // No char format changes yet
|
|
_fSeenFontTable = FALSE; // No \fonttbl yet
|
|
_fCharSet = FALSE; // No \fcharset yet
|
|
_fNon0CharSet = FALSE; // No nonANSI_CHARSET \fcharset yet
|
|
_dwFlagsUnion = 0; // No flags yet
|
|
_fNotifyLowFiRTF = (_ped->_dwEventMask & ENM_LOWFIRTF) != 0;
|
|
_fBody = FALSE; // Wait till something's inserted
|
|
_pes->dwError = 0; // No error yet
|
|
_cchUsedNumText = 0; // No numbering text yet
|
|
_cTab = 0;
|
|
_pstateStackTop = NULL;
|
|
_pstateLast = NULL;
|
|
_szText =
|
|
_pchRTFBuffer = // No input buffer yet
|
|
_pchRTFCurrent =
|
|
_pchRTFEnd = NULL;
|
|
_prtfObject = NULL;
|
|
_pcpObPos = NULL;
|
|
_bTabLeader = 0;
|
|
_bTabType = 0;
|
|
_bShapeNameIndex = 0;
|
|
_pobj = 0;
|
|
_fSymbolField = FALSE;
|
|
_fMac = FALSE;
|
|
_fNo_iTabsTable = FALSE;
|
|
_fRTLRow = FALSE;
|
|
_dwRowResolveFlags = 0;
|
|
_bTableLevelIP = 0;
|
|
_iTabsLevel1 = -1;
|
|
InitializeTableRowParms(); // Initialize table parms
|
|
|
|
// Does story size exceed the maximum text size? Be very careful to
|
|
// use unsigned comparisons here since _cchMax has to be unsigned
|
|
// (EM_LIMITTEXT allows 0xFFFFFFFF to be a large positive maximum
|
|
// value). I.e., don't use signed arithmetic.
|
|
DWORD cchAdj = _ped->GetAdjustedTextLength();
|
|
_cchMax = _ped->TxGetMaxLength();
|
|
|
|
if(_cchMax > cchAdj)
|
|
_cchMax = _cchMax - cchAdj; // Room left
|
|
else
|
|
_cchMax = 0; // No room left
|
|
|
|
ZeroMemory(_rgStyles, sizeof(_rgStyles)); // No style levels yet
|
|
|
|
_iCharRepBiDi = 0;
|
|
if(_ped->IsBiDi())
|
|
{
|
|
_iCharRepBiDi = ARABIC_INDEX; // Default Arabic charset
|
|
|
|
BYTE iCharRep;
|
|
CFormatRunPtr rpCF(prg->_rpCF);
|
|
|
|
// Look backward in text, trying to find a RTL CharRep.
|
|
// NB: \fN with an RTL charset updates _iCharRepBiDi.
|
|
do
|
|
{
|
|
iCharRep = _ped->GetCharFormat(rpCF.GetFormat())->_iCharRep;
|
|
if(IsRTLCharRep(iCharRep))
|
|
{
|
|
_iCharRepBiDi = iCharRep;
|
|
break;
|
|
}
|
|
} while (rpCF.PrevRun());
|
|
}
|
|
|
|
// Initialize OleStream
|
|
RTFReadOLEStream.Reader = this;
|
|
RTFReadOLEStream.lpstbl->Get = (DWORD (CALLBACK*)(LPOLESTREAM, void *, DWORD))
|
|
RTFGetFromStream;
|
|
RTFReadOLEStream.lpstbl->Put = NULL;
|
|
|
|
#if defined(DEBUG) && !defined(NOFULLDEBUG)
|
|
_fTestingParserCoverage = FALSE;
|
|
_prtflg = NULL;
|
|
|
|
if(GetProfileIntA("RICHEDIT DEBUG", "RTFLOG", 0))
|
|
{
|
|
_prtflg = new CRTFLog;
|
|
if(_prtflg && !_prtflg->FInit())
|
|
{
|
|
delete _prtflg;
|
|
_prtflg = NULL;
|
|
}
|
|
AssertSz(_prtflg, "CRTFRead::CRTFRead: Error creating RTF log");
|
|
}
|
|
#endif // DEBUG
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleStartGroup()
|
|
*
|
|
* @mfunc
|
|
* Handle start of new group. Alloc and push new state onto state
|
|
* stack
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleStartGroup()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleStartGroup");
|
|
|
|
STATE * pstate = _pstateStackTop;
|
|
STATE * pstateNext = NULL;
|
|
|
|
if(pstate) // At least one STATE already
|
|
{ // allocated
|
|
Apply_CF(); // Apply any collected char
|
|
// Note (igorzv) we don't Apply_PF() here so as not to change para
|
|
// properties before we run into \par i.e. not to use paragraph
|
|
// properties if we copy only one word from paragraph. We can use an
|
|
// assertion here that neither we nor Word use end of group for
|
|
// restoring paragraph properties. So everything will be OK with stack
|
|
pstate->iCF = (SHORT)_prg->Get_iCF(); // Save current CF
|
|
pstate = pstate->pstateNext; // Use previously allocated
|
|
if(pstate) // STATE frame if it exists
|
|
pstateNext = pstate->pstateNext; // It does; save its forward
|
|
} // link for restoration below
|
|
|
|
if(!pstate) // No new STATE yet: alloc one
|
|
{
|
|
pstate = new STATE();
|
|
if(!pstate) // Couldn't alloc new STATE
|
|
goto memerror;
|
|
|
|
_pstateLast = pstate; // Update ptr to last STATE
|
|
} // alloc'd
|
|
|
|
STATE *pstateGetsPF;
|
|
|
|
// Apply the accumulated PF delta's to the old current state or, if there
|
|
// is no current state, to the newly created state.
|
|
pstateGetsPF = _pstateStackTop ? _pstateStackTop : pstate;
|
|
if(!pstateGetsPF->AddPF(_PF, _bDocType, _dwMaskPF, _dwMaskPF2))
|
|
goto memerror;
|
|
|
|
_dwMaskPF = _dwMaskPF2 = 0; // _PF contains delta's from
|
|
// *_pstateStackTop->pPF
|
|
if(_pstateStackTop) // There's a previous STATE
|
|
{
|
|
*pstate = *_pstateStackTop; // Copy current state info
|
|
// N.B. This will cause the current and previous state to share
|
|
// the same PF. PF delta's are accumulated in _PF. A new PF
|
|
// is created for _pstateStackTop when the _PF deltas are applied.
|
|
|
|
_pstateStackTop->pstateNext = pstate;
|
|
}
|
|
else // Setup default for first state
|
|
{
|
|
pstate->nCodePage = IsUTF8 ? CP_UTF8 : _nCodePage;
|
|
|
|
for(LONG i = ARRAY_SIZE(pstate->rgDefFont); i--; )
|
|
pstate->rgDefFont[i].sHandle = -1; // Default undefined associate
|
|
}
|
|
|
|
pstate->pstatePrev = _pstateStackTop; // Link STATEs both ways
|
|
pstate->pstateNext = pstateNext;
|
|
_pstateStackTop = pstate; // Push stack
|
|
|
|
done:
|
|
TRACEERRSZSC("HandleStartGroup()", -_ecParseError);
|
|
return _ecParseError;
|
|
|
|
memerror:
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecStackOverflow;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleEndGroup()
|
|
*
|
|
* @mfunc
|
|
* Handle end of new group
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleEndGroup()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleEndGroup");
|
|
|
|
STATE * pstate = _pstateStackTop;
|
|
STATE * pstatePrev;
|
|
|
|
Assert(_PF._iTabs == -1);
|
|
|
|
if(!pstate) // No stack to pop
|
|
{
|
|
_ecParseError = ecStackUnderflow;
|
|
goto done;
|
|
}
|
|
|
|
_pstateStackTop = // Pop stack
|
|
pstatePrev = pstate->pstatePrev;
|
|
|
|
if(!pstatePrev)
|
|
{
|
|
Assert(pstate->pPF);
|
|
|
|
// We're ending the parse. Copy the final PF into _PF so that
|
|
// subsequent calls to Apply_PF will have a PF to apply.
|
|
_PF = *pstate->pPF;
|
|
//TODO honwch _dwMaskPF = pstate->dwMaskPF;
|
|
_PF._iTabs = -1; // Force recache
|
|
_PF._wEffects &= ~PFE_TABLE;
|
|
}
|
|
|
|
// Adjust the PF for the new _pstateStackTop and delete unused PF's.
|
|
if(pstate->sDest == destParaNumbering || pstate->sDest == destParaNumText)
|
|
{
|
|
if(pstatePrev && pstate->pPF != pstatePrev->pPF)
|
|
{
|
|
// Bleed the PF of the current state into the previous state for
|
|
// paragraph numbering groups
|
|
Assert(pstatePrev->pPF);
|
|
pstatePrev->DeletePF();
|
|
pstatePrev->pPF = pstate->pPF;
|
|
pstate->pPF = NULL;
|
|
}
|
|
else
|
|
pstate->DeletePF();
|
|
// N.B. Here, we retain the _PF diffs since they apply to the
|
|
// enclosing group along with the PF of the group we are leaving
|
|
}
|
|
else
|
|
{
|
|
// We're popping the state, so delete its PF and discard the _PF diffs
|
|
Assert(pstate->pPF);
|
|
pstate->DeletePF();
|
|
|
|
// If !pstatePrev, we're ending the parse, in which case the _PF
|
|
// structure contains final PF (so don't toast it).
|
|
if(pstatePrev)
|
|
_dwMaskPF = _dwMaskPF2 = 0;
|
|
}
|
|
|
|
if(pstatePrev)
|
|
{
|
|
LONG i;
|
|
_dwMaskCF = _dwMaskCF2 = 0; // Discard any CF deltas
|
|
|
|
switch(pstate->sDest)
|
|
{
|
|
case destParaNumbering:
|
|
// {\pn ...}
|
|
pstatePrev->sIndentNumbering = pstate->sIndentNumbering;
|
|
pstatePrev->fBullet = pstate->fBullet;
|
|
break;
|
|
|
|
case destObject:
|
|
// Clear our object flags just in case we have corrupt RTF
|
|
if(_fNeedPres)
|
|
{
|
|
_fNeedPres = FALSE;
|
|
_fNeedIcon = FALSE;
|
|
_pobj = NULL;
|
|
}
|
|
break;
|
|
|
|
case destFontTable:
|
|
if(pstatePrev->sDest == destFontTable)
|
|
{
|
|
// Leaving subgroup within \fonttbl group
|
|
break;
|
|
}
|
|
|
|
// Leaving {\fonttbl...} group
|
|
_fSeenFontTable = TRUE;
|
|
|
|
// Default font should now be defined, so select it (this
|
|
// creates CF deltas).
|
|
SetPlain(pstate);
|
|
|
|
if(_sDefaultFont != -1)
|
|
{
|
|
pstate->rgDefFont[DEFFONT_LTRCH].sHandle = _sDefaultFont;
|
|
|
|
Assert(pstate->pstatePrev);
|
|
if (pstate->pstatePrev)
|
|
{
|
|
pstate->pstatePrev->rgDefFont[DEFFONT_LTRCH].sHandle = _sDefaultFont;
|
|
Assert(pstate->pstatePrev->pstatePrev == NULL);
|
|
}
|
|
}
|
|
|
|
if(_sDefaultBiDiFont != -1)
|
|
{
|
|
// Validate default BiDi font since Word 2000 may choose
|
|
// a nonBiDi font
|
|
i = _fonts.Count();
|
|
TEXTFONT *ptf = _fonts.Elem(0);
|
|
for(; i-- && _sDefaultBiDiFont != ptf->sHandle; ptf++)
|
|
;
|
|
|
|
if(i < 0 || !IsRTLCharRep(ptf->iCharRep))
|
|
{
|
|
_sDefaultBiDiFont = -1;
|
|
|
|
// Bad DefaultBiDiFont, find the first good bidi font
|
|
i = _fonts.Count();
|
|
ptf = _fonts.Elem(0);
|
|
for (; i--; ptf++)
|
|
{
|
|
if (IsRTLCharRep(ptf->iCharRep))
|
|
{
|
|
_sDefaultBiDiFont = ptf->sHandle;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(_sDefaultBiDiFont != -1)
|
|
{
|
|
pstate->rgDefFont[DEFFONT_RTLCH].sHandle = _sDefaultBiDiFont;
|
|
|
|
Assert(pstate->pstatePrev);
|
|
if (pstate->pstatePrev)
|
|
{
|
|
pstate->pstatePrev->rgDefFont[DEFFONT_RTLCH].sHandle = _sDefaultBiDiFont;
|
|
Assert(pstate->pstatePrev->pstatePrev == NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that a document-level codepage has been determined and
|
|
// then scan the font names and retry the conversion to Unicode,
|
|
// if necessary.
|
|
if(_nCodePage == INVALID_CODEPAGE)
|
|
{
|
|
// We haven't determined a document-level codepage
|
|
// from the \ansicpgN tag, nor from the font table
|
|
// \fcharsetN and \cpgN values. As a last resort,
|
|
// let's use the \deflangN and \deflangfeN tags
|
|
LANGID langid;
|
|
|
|
if(_sDefaultLanguageFE != INVALID_LANGUAGE)
|
|
langid = _sDefaultLanguageFE;
|
|
|
|
else if(_sDefaultLanguage != INVALID_LANGUAGE &&
|
|
_sDefaultLanguage != sLanguageEnglishUS)
|
|
{
|
|
// _sDefaultLanguage == sLanguageEnglishUS is inreliable
|
|
// in the absence of \deflangfeN. Many FE RTF writers
|
|
// write \deflang1033 (sLanguageEnglishUS).
|
|
langid = _sDefaultLanguage;
|
|
}
|
|
else if(_dwFlags & SFF_SELECTION)
|
|
{
|
|
// For copy/paste case, if nothing available, try the system
|
|
// default langid. This is to fix FE Excel95 problem.
|
|
langid = GetSystemDefaultLangID();
|
|
}
|
|
else
|
|
goto NoLanguageInfo;
|
|
|
|
_nCodePage = CodePageFromCharRep(CharRepFromLID(langid));
|
|
}
|
|
|
|
NoLanguageInfo:
|
|
if(_nCodePage == INVALID_CODEPAGE)
|
|
break;
|
|
|
|
// Fixup misconverted font face names
|
|
for(i = 0; i < _fonts.Count(); i++)
|
|
{
|
|
TEXTFONT *ptf = _fonts.Elem(i);
|
|
|
|
if (ptf->sCodePage == INVALID_CODEPAGE ||
|
|
ptf->sCodePage == CP_SYMBOL)
|
|
{
|
|
if(ptf->fNameIsDBCS)
|
|
{
|
|
char szaTemp[LF_FACESIZE];
|
|
BOOL fMissingCodePage;
|
|
|
|
// Unconvert misconverted face name
|
|
SideAssert(WCTMB(ptf->sCodePage, 0,
|
|
ptf->szName, -1,
|
|
szaTemp, sizeof(szaTemp),
|
|
NULL, NULL, &fMissingCodePage) > 0);
|
|
Assert(ptf->sCodePage == CP_SYMBOL ||
|
|
fMissingCodePage);
|
|
|
|
// Reconvert face name using new codepage info
|
|
SideAssert(MBTWC(_nCodePage, 0,
|
|
szaTemp, -1,
|
|
ptf->szName, sizeof(ptf->szName),
|
|
&fMissingCodePage) > 0);
|
|
|
|
if(!fMissingCodePage)
|
|
ptf->fNameIsDBCS = FALSE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
_prg->Set_iCF(pstatePrev->iCF); // Restore previous CharFormat
|
|
ReleaseFormats(pstatePrev->iCF, -1);
|
|
}
|
|
|
|
done:
|
|
TRACEERRSZSC("HandleEndGroup()", - _ecParseError);
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleFieldEndGroup()
|
|
*
|
|
* @mfunc
|
|
* Handle end of \field
|
|
*/
|
|
//FUTURE (keithcu) If I knew the first thing about RTF, I'd cleanup the symbol handling.
|
|
void CRTFRead::HandleFieldEndGroup()
|
|
{
|
|
STATE * pstate = _pstateStackTop;
|
|
|
|
if (!IN_RANGE(destField, pstate->sDest, destFieldInstruction) ||
|
|
pstate->sDest != destField && !_ecParseError)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// For SYMBOLS
|
|
if(_fSymbolField)
|
|
{
|
|
_fSymbolField = FALSE;
|
|
return;
|
|
}
|
|
|
|
// Walk backwards, removing the STARTFIELD and SEPARATOR character.
|
|
// Make the instruction field hidden, and mark the whole range
|
|
// with CFE_LINK | CFE_LINKPROTECTED so that our automatic URL
|
|
// detection code doesn't get in the way and remove this stuff.
|
|
// If it is not a hyperlink field, delete fldinst.
|
|
CTxtRange rg(*_prg);
|
|
long cchInst = -2, cchResult = -2;
|
|
WCHAR ch, chNext = 0;
|
|
// LONG cpSave = _prg->GetCp();
|
|
|
|
rg.SetIgnoreFormatUpdate(TRUE);
|
|
|
|
// Find boundary between instruction and result field
|
|
while(!IN_RANGE(STARTFIELD, (ch = rg._rpTX.GetChar()), SEPARATOR) || chNext != 'F')
|
|
{
|
|
if(!rg.Move(-1, FALSE))
|
|
return; // Nothing to fixup
|
|
cchResult++;
|
|
chNext = ch;
|
|
}
|
|
|
|
if (ch == SEPARATOR)
|
|
{
|
|
rg.Move(2, TRUE);
|
|
rg.ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
}
|
|
else //No field result
|
|
{
|
|
cchInst = cchResult;
|
|
cchResult = 0;
|
|
}
|
|
BOOL fBackSlash = FALSE;
|
|
chNext = 0;
|
|
while((ch = rg.CRchTxtPtr::GetChar()) != STARTFIELD || chNext != 'F')
|
|
{
|
|
if(ch == BSLASH)
|
|
fBackSlash = TRUE; // Need backslash pass
|
|
if(!rg.Move(-1, FALSE))
|
|
return; // Nothing to fix up
|
|
cchInst++;
|
|
chNext = ch;
|
|
}
|
|
rg.Move(_ecParseError ? tomForward : 2, TRUE);
|
|
rg.ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
if(_ecParseError)
|
|
return;
|
|
|
|
// If it's a hyperlink field, set properties, else, delete fldinst
|
|
CTxtPtr tp(rg._rpTX);
|
|
if(tp.FindText(rg.GetCp() + cchInst, FR_DOWN, L"HYPERLINK", 9) != -1)
|
|
{
|
|
while((ch = tp.GetChar()) == ' ' || ch == '\"')
|
|
tp.Move(1);
|
|
ch = tp.GetPrevChar();
|
|
|
|
if(ch == '\"' && fBackSlash) // Word quadruples backslash, so
|
|
{ // need to delete every other one
|
|
LONG cp0 = rg.GetCp(); // Save cp at start of instruction
|
|
LONG cp1 = tp.GetCp(); // Save cp at start of URL
|
|
LONG cpMax = cp0 + cchInst; // Max cp for the instruction
|
|
|
|
rg.SetCp(cp1, FALSE);
|
|
while(rg.GetCp() < cpMax)
|
|
{
|
|
ch = rg._rpTX.GetChar();
|
|
if(ch == '\"')
|
|
break;
|
|
if(ch == BSLASH)
|
|
{
|
|
if (!rg.Move(1, TRUE))
|
|
break;
|
|
|
|
ch = rg._rpTX.GetChar();
|
|
if(ch == '\"')
|
|
break;
|
|
if(ch == BSLASH)
|
|
{
|
|
cchInst--;
|
|
rg.ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
}
|
|
}
|
|
|
|
if (!rg.Move(1, FALSE))
|
|
break;
|
|
}
|
|
rg.SetCp(cp0, FALSE); // Restore rg and tp
|
|
tp = rg._rpTX; // Rebind tp, since deletions may
|
|
tp.SetCp(cp1); // change plain-text arrays
|
|
}
|
|
CCharFormat CF;
|
|
DWORD dwMask, dwMask2;
|
|
LONG cch1 = 0;
|
|
CTxtPtr tp1(_prg->_rpTX);
|
|
|
|
tp1.Move(-cchResult); // Point at start of link result
|
|
for(LONG cch = cchResult; cch; cch--)
|
|
{
|
|
DWORD ch1 = tp.GetChar();
|
|
if(ch1 != tp1.GetChar())
|
|
break;
|
|
if(ch1 == ' ')
|
|
cch1 = 1;
|
|
tp.Move(1); // This could be much
|
|
tp1.Move(1); // faster if it matters
|
|
}
|
|
if(!cch && tp.GetChar() == ch) // Perfect match, so delete
|
|
{ // instruction and use built-in
|
|
WCHAR chLeftBracket = '<'; // RichEdit URLs. Store autocolor
|
|
rg.Move(cchInst, TRUE); // and no underlining
|
|
rg.ReplaceRange(cch1, &chLeftBracket, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
CF._dwEffects = CFE_LINK | CFE_AUTOCOLOR;
|
|
CF._crTextColor = 0; // Won't be used but will be stored
|
|
dwMask = CFM_LINK | CFM_COLOR | CFM_UNDERLINE;
|
|
dwMask2 = 0;
|
|
if(cch1)
|
|
{
|
|
WCHAR chRightBracket = '>';
|
|
_prg->ReplaceRange(cch1, &chRightBracket, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rg.Move(cchInst, TRUE); // Set properties on instruction
|
|
CF._dwEffects = CFE_HIDDEN | CFE_LINK | CFE_LINKPROTECTED ;
|
|
rg.SetCharFormat(&CF, 0, 0, CFM_LINK | CFM_HIDDEN, CFM2_LINKPROTECTED);
|
|
dwMask = CFM_LINK;
|
|
dwMask2 = CFM2_LINKPROTECTED;
|
|
}
|
|
rg.Set(rg.GetCp(), -cchResult); // Set properties on result
|
|
rg.SetCharFormat(&CF, 0, 0, dwMask, dwMask2);
|
|
}
|
|
else
|
|
{
|
|
_iKeyword = i_field; // Might be nice to have field name
|
|
if((tp.GetChar() | 0x20) == 'e') // Only fire notification for EQ fields
|
|
{
|
|
tp.Move(1);
|
|
if((tp.GetChar() | 0x20) == 'q')
|
|
{
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
if(HandleEq(rg, tp) == ecNoError)
|
|
return;
|
|
}
|
|
}
|
|
rg.Move(cchInst, TRUE);
|
|
rg.ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL, RR_NO_LP_CHECK);
|
|
}
|
|
// if(_cpThisPara > rg.GetCp()) // Field result had \par's, so
|
|
// _cpThisPara -= cpSave - _prg->GetCp();// subtract cch deleted
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::SelectCurrentFont(iFont)
|
|
*
|
|
* @mfunc
|
|
* Set active font to that with index <p iFont>. Take into account
|
|
* bad font numbers.
|
|
*/
|
|
void CRTFRead::SelectCurrentFont(
|
|
INT iFont) //@parm Font handle of font to select
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::SelectCurrentFont");
|
|
|
|
LONG i = _fonts.Count();
|
|
STATE * pstate = _pstateStackTop;
|
|
TEXTFONT * ptf = _fonts.Elem(0);
|
|
|
|
AssertSz(i, "CRTFRead::SelectCurrentFont: bad font collection");
|
|
|
|
for(; i-- && iFont != ptf->sHandle; ptf++) // Search for font with handle
|
|
; // iFont
|
|
|
|
// Font handle not found: use default, which is valid
|
|
// since \rtf copied _prg's
|
|
if(i < 0)
|
|
ptf = _fonts.Elem(0);
|
|
|
|
BOOL fDefFontFromSystem = (i == (LONG)_fonts.Count() - 1 || i < 0) &&
|
|
!_fReadDefFont;
|
|
|
|
_CF._iFont = GetFontNameIndex(ptf->szName);
|
|
_dwMaskCF2 |= CFM2_FACENAMEISDBCS;
|
|
_CF._dwEffects &= ~CFE_FACENAMEISDBCS;
|
|
if(ptf->fNameIsDBCS)
|
|
_CF._dwEffects |= CFE_FACENAMEISDBCS;
|
|
|
|
if(pstate->sDest != destFontTable)
|
|
{
|
|
_CF._iCharRep = ptf->iCharRep;
|
|
_CF._bPitchAndFamily = ptf->bPitchAndFamily;
|
|
_dwMaskCF |= CFM_FACE | CFM_CHARSET;
|
|
if (IsRTLCharRep(_CF._iCharRep) && ptf->sCodePage == 1252)
|
|
ptf->sCodePage = (SHORT)CodePageFromCharRep(_CF._iCharRep); // Fix sCodePage to match charset
|
|
}
|
|
|
|
if (_ped->Get10Mode() && !_fSeenFontTable &&
|
|
_nCodePage == INVALID_CODEPAGE && ptf->sCodePage == 1252)
|
|
{
|
|
if (W32->IsFECodePage(GetACP()))
|
|
_nCodePage = GetACP();
|
|
}
|
|
|
|
// Ensure that the state's codepage is not supplied by the system.
|
|
// That is, if we are using the codepage info from the default font,
|
|
// be sure that the default font info was read from the RTF file.
|
|
pstate->SetCodePage((fDefFontFromSystem && _nCodePage != INVALID_CODEPAGE) ||
|
|
ptf->sCodePage == INVALID_CODEPAGE
|
|
? _nCodePage : ptf->sCodePage);
|
|
pstate->ptf = ptf;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::SetPlain(pstate)
|
|
*
|
|
* @mfunc
|
|
* Setup _CF for \plain
|
|
*/
|
|
void CRTFRead::SetPlain(
|
|
STATE *pstate)
|
|
{
|
|
ZeroMemory(&_CF, sizeof(CCharFormat));
|
|
|
|
_dwMaskCF = CFM_ALL2;
|
|
_dwMaskCF2 = CFM2_LINKPROTECTED;
|
|
if(_dwFlags & SFF_SELECTION && _prg->GetCp() == _cpFirst && !_fCharSet)
|
|
{
|
|
// Let NT 4.0 CharMap use insertion point size
|
|
_CF._yHeight = _ped->GetCharFormat(_prg->Get_iFormat())->_yHeight;
|
|
}
|
|
else
|
|
_CF._yHeight = PointsToFontHeight(yDefaultFontSize);
|
|
|
|
_CF._dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR; // Set default effects
|
|
if(_sDefaultLanguage == INVALID_LANGUAGE)
|
|
_dwMaskCF &= ~CFM_LCID;
|
|
else
|
|
_CF._lcid = MAKELCID((WORD)_sDefaultLanguage, SORT_DEFAULT);
|
|
|
|
_CF._bUnderlineType = CFU_UNDERLINE;
|
|
SelectCurrentFont(_sDefaultFont);
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::ReadFontName(pstate, iAllASCII)
|
|
*
|
|
* @mfunc
|
|
* read font name _szText into <p pstate>->ptf->szName and deal with
|
|
* tagged fonts
|
|
*/
|
|
void CRTFRead::ReadFontName(
|
|
STATE * pstate, //@parm state whose font name is to be read into
|
|
int iAllASCII) //@parm indicates that _szText is all ASCII chars
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::ReadFontName");
|
|
|
|
if (pstate->ptf)
|
|
{
|
|
INT cchName = LF_FACESIZE - 1;
|
|
WCHAR * pchDst = pstate->ptf->szName;
|
|
char * pachName = (char *)_szText ;
|
|
|
|
// Append additional text from _szText to TEXTFONT::szName
|
|
|
|
// We need to append here since some RTF writers decide
|
|
// to break up a font name with other RTF groups
|
|
while(*pchDst && cchName > 0)
|
|
{
|
|
pchDst++;
|
|
cchName--;
|
|
}
|
|
if(!cchName) // Probably illegal file
|
|
{ // e.g., extra {
|
|
_ecParseError = ecFontTableOverflow;
|
|
return;
|
|
}
|
|
INT cchLimit = cchName;
|
|
BOOL fTaggedName = FALSE;
|
|
while (*pachName &&
|
|
*pachName != ';' &&
|
|
cchLimit) // Remove semicolons
|
|
{
|
|
pachName++;
|
|
cchLimit--;
|
|
|
|
if (*pachName == '(')
|
|
fTaggedName = TRUE;
|
|
}
|
|
*pachName = '\0';
|
|
|
|
// Use the codepage of the font in all cases except where the font uses
|
|
// the symbol charset (and the codepage has been mapped from the charset)
|
|
// and UTF-8 isn't being used
|
|
LONG nCodePage = pstate->nCodePage != CP_SYMBOL
|
|
? pstate->nCodePage : _nCodePage;
|
|
|
|
BOOL fMissingCodePage;
|
|
Assert(!IsUTF8 || nCodePage == CP_UTF8);
|
|
INT cch = MBTWC(nCodePage, 0,
|
|
(char *)_szText, -1,
|
|
pchDst, cchName, &fMissingCodePage);
|
|
|
|
if(cch > 0 && fMissingCodePage && iAllASCII == CONTAINS_NONASCII)
|
|
pstate->ptf->fNameIsDBCS = TRUE;
|
|
else if(pstate->ptf->iCharRep == DEFAULT_INDEX &&
|
|
W32->IsFECodePage(nCodePage) &&
|
|
GetTrailBytesCount(*_szText, nCodePage))
|
|
pstate->ptf->iCharRep = CharRepFromCodePage(nCodePage); // Fix up charset
|
|
|
|
|
|
// Make sure destination is null terminated
|
|
if(cch > 0)
|
|
pchDst[cch] = 0;
|
|
|
|
// Fall through even if MBTWC <= 0, since we may be appending text to an
|
|
// existing font name.
|
|
|
|
if(pstate->ptf == _fonts.Elem(0)) // If it's the default font,
|
|
SelectCurrentFont(_sDefaultFont); // update _CF accordingly
|
|
|
|
WCHAR * szNormalName;
|
|
|
|
if(pstate->ptf->iCharRep && pstate->fRealFontName)
|
|
{
|
|
// if we don't know about this font don't use the real name
|
|
if(!FindTaggedFont(pstate->ptf->szName,
|
|
pstate->ptf->iCharRep, &szNormalName))
|
|
{
|
|
pstate->fRealFontName = FALSE;
|
|
pstate->ptf->szName[0] = 0;
|
|
}
|
|
}
|
|
else if(IsTaggedFont(pstate->ptf->szName,
|
|
&pstate->ptf->iCharRep, &szNormalName))
|
|
{
|
|
wcscpy(pstate->ptf->szName, szNormalName);
|
|
pstate->ptf->sCodePage = (SHORT)CodePageFromCharRep(pstate->ptf->iCharRep);
|
|
pstate->SetCodePage(pstate->ptf->sCodePage);
|
|
}
|
|
else if(fTaggedName && !fMissingCodePage)
|
|
{
|
|
HDC hDC = W32->GetScreenDC();
|
|
if (!W32->IsFontAvail( hDC, 0, 0, NULL, pstate->ptf->szName))
|
|
{
|
|
// Fix up tagged name by removing characters after the ' ('
|
|
INT i = 0;
|
|
WCHAR *pwchTag = pstate->ptf->szName;
|
|
|
|
while (pwchTag[i] && pwchTag[i] != L'(') // Search for '('
|
|
i++;
|
|
|
|
if(pwchTag[i] && i > 0)
|
|
{
|
|
while (i > 0 && pwchTag[i-1] == 0x20) // Remove spaces before the '('
|
|
i--;
|
|
pwchTag[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::GetColor (dwMask)
|
|
*
|
|
* @mfunc
|
|
* Store the autocolor or autobackcolor effect bit and return the
|
|
* COLORREF for color _iParam
|
|
*
|
|
* @rdesc
|
|
* COLORREF for color _iParam
|
|
*
|
|
* @devnote
|
|
* If the entry in _colors corresponds to tomAutoColor, gets the value
|
|
* RGB(0,0,0) (since no \red, \green, and \blue fields are used), but
|
|
* isn't used by the RichEdit engine. Entry 1 corresponds to the first
|
|
* explicit entry in the \colortbl and is usually RGB(0,0,0). The _colors
|
|
* table is built by HandleToken() when it handles the token tokenText
|
|
* for text consisting of a ';' for a destination destColorTable.
|
|
*/
|
|
COLORREF CRTFRead::GetColor(
|
|
DWORD dwMask) //@parm Color mask bit
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::GetColor");
|
|
|
|
if(_iParam < 0 || _iParam >= _colors.Count()) // Illegal _iParam
|
|
return RGB(0,0,0);
|
|
|
|
COLORREF Color = *_colors.Elem(_iParam);
|
|
|
|
if(dwMask)
|
|
{
|
|
_dwMaskCF |= dwMask; // Turn on appropriate mask bit
|
|
_CF._dwEffects &= ~dwMask; // auto(back)color off: color is to be used
|
|
|
|
if(Color == tomAutoColor)
|
|
{
|
|
_CF._dwEffects |= dwMask; // auto(back)color on
|
|
Color = RGB(0,0,0);
|
|
}
|
|
}
|
|
return Color;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::GetStandardColorIndex ()
|
|
*
|
|
* @mfunc
|
|
* Return the color index into the standard 16-entry Word \colortbl
|
|
* corresponding to the color index _iParam for the current \colortbl
|
|
*
|
|
* @rdesc
|
|
* Standard color index corresponding to the color associated with _iParam
|
|
*/
|
|
LONG CRTFRead::GetStandardColorIndex()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::GetColorIndex");
|
|
|
|
if(_iParam < 0 || _iParam >= _colors.Count()) // Illegal _iParam:
|
|
return 0; // use autocolor
|
|
|
|
COLORREF Color = *_colors.Elem(_iParam);
|
|
|
|
for(LONG i = 0; i < 16; i++)
|
|
{
|
|
if(Color == g_Colors[i])
|
|
return i + 1;
|
|
}
|
|
return 0; // Not there: use autocolor
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::GetCellColorIndex ()
|
|
*
|
|
* @mfunc
|
|
* Return the color index into the standard 16-entry Word \colortbl
|
|
* corresponding to the color index _iParam for the current \colortbl.
|
|
* If color isn't found, use _crCellCustom1 or _crCellCustom2.
|
|
*
|
|
* @rdesc
|
|
* Standard or custom color index corresponding to the color associated
|
|
* with _iParam
|
|
*/
|
|
LONG CRTFRead::GetCellColorIndex()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::GetColorIndex");
|
|
|
|
LONG i = GetStandardColorIndex(); // 16 standard colors (1 - 16)
|
|
if(i || _iParam >= _colors.Count() || _iParam < 0) // plus autocolor (0)
|
|
return i;
|
|
|
|
COLORREF Color = *_colors.Elem(_iParam);// Not there: try using custom colors
|
|
if(!_crCellCustom1 || Color == _crCellCustom1)
|
|
{
|
|
_crCellCustom1 = Color; // First custom cr
|
|
return 17;
|
|
}
|
|
|
|
if(!_crCellCustom2 || Color == _crCellCustom2)
|
|
{
|
|
_crCellCustom2 = Color; // Second custom cr
|
|
return 18;
|
|
}
|
|
return 0; // No custom cr available
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleEq(&rg, &tp)
|
|
*
|
|
* @mfunc
|
|
* Handle Word EQ field
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleEq(
|
|
CTxtRange & rg, //@parm Range to use
|
|
CTxtPtr & tp) //@parm TxtPtr to use
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleEq");
|
|
|
|
#if 0
|
|
while(tp.GetCp() < _prg->GetCp())
|
|
{
|
|
}
|
|
#endif
|
|
return ecGeneralFailure; // Not yet implemented, but don't set _ecParseError
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleCell()
|
|
*
|
|
* @mfunc
|
|
* Handle \cell command
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
void CRTFRead::HandleCell()
|
|
{
|
|
if(!_bTableLevel)
|
|
{
|
|
if(!_fStartRow)
|
|
return;
|
|
DelimitRow(szRowStart); // \cell directly follows \row
|
|
}
|
|
if(_bTableLevel + _bTableLevelIP > MAXTABLENEST)
|
|
{
|
|
_iCell++;
|
|
HandleChar(TAB);
|
|
return;
|
|
}
|
|
LONG cCell = _cCell;
|
|
CTabs * pTabs = NULL;
|
|
CELLPARMS *pCellParms = NULL;
|
|
|
|
if(_bTableLevel == 1 && _iTabsLevel1 >= 0 && !_fNo_iTabsTable)
|
|
{
|
|
pTabs = GetTabsCache()->Elem(_iTabsLevel1);
|
|
pCellParms = (CELLPARMS *)(pTabs->_prgxTabs);
|
|
cCell = pTabs->_cTab/(CELL_EXTRA + 1);
|
|
}
|
|
if(!cCell) // _rgxCell has no cell defs
|
|
{
|
|
if(_iTabsTable < 0) // No cells defined yet
|
|
{
|
|
_iCell++; // Count cells inserted
|
|
HandleEndOfPara(); // Insert cell delimiter
|
|
return;
|
|
}
|
|
// Use previous table defs
|
|
Assert(_bTableLevel == 1 && !_fNo_iTabsTable);
|
|
|
|
pTabs = GetTabsCache()->Elem(_iTabsTable);
|
|
cCell = pTabs->_cTab/(CELL_EXTRA + 1);
|
|
}
|
|
if(_iCell < cCell) // Don't add more cells than
|
|
{ // defined, since Word crashes
|
|
if(pCellParms && IsLowCell(pCellParms[_iCell].uCell))
|
|
HandleChar(NOTACHAR); // Signal pseudo cell (\clvmrg)
|
|
_iCell++; // Count cells inserted
|
|
HandleEndOfPara(); // Insert cell delimiter
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleCellx(iParam)
|
|
*
|
|
* @mfunc
|
|
* Handle \cell command
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
void CRTFRead::HandleCellx(
|
|
LONG iParam)
|
|
{
|
|
if(_cCell < MAX_TABLE_CELLS) // Save cell right boundaries
|
|
{
|
|
if(!_cCell)
|
|
{ // Save border info
|
|
_wBorders = _PF._wBorders;
|
|
_wBorderSpace = _PF._wBorderSpace;
|
|
_wBorderWidth = _PF._wBorderWidth;
|
|
_xCellPrev = _xRowOffset;
|
|
}
|
|
CELLPARMS *pCellParms = (CELLPARMS *)&_rgxCell[0];
|
|
// Store cell widths instead of right boundaries relative to \trleftN
|
|
LONG i = iParam - _xCellPrev;
|
|
i = max(i, 0);
|
|
i = min(i, 1440*22);
|
|
pCellParms[_cCell].uCell = i + (_bCellFlags << 24);
|
|
pCellParms[_cCell].dxBrdrWidths = _dwCellBrdrWdths;
|
|
pCellParms[_cCell].dwColors = _dwCellColors;
|
|
pCellParms[_cCell].bShading = (BYTE)min(200, _dwShading);
|
|
_dwCellColors = 0; // Default autocolor for next cell
|
|
_dwShading = 0;
|
|
_xCellPrev = iParam;
|
|
_cCell++;
|
|
_dwCellBrdrWdths = 0;
|
|
_bCellFlags = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleChar(ch)
|
|
*
|
|
* @mfunc
|
|
* Handle single Unicode character <p ch>
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleChar(
|
|
WCHAR ch) //@parm char token to be handled
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleChar");
|
|
|
|
if(!_ped->_pdp->IsMultiLine() && IsASCIIEOP(ch))
|
|
_ecParseError = ecTruncateAtCRLF;
|
|
else
|
|
{
|
|
AssertNr(ch <= 0x7F || ch > 0xFF || FTokIsSymbol(ch));
|
|
_dwMaskCF2 |= CFM2_RUNISDBCS;
|
|
_CF._dwEffects &= ~CFE_RUNISDBCS;
|
|
AddText(&ch, 1, CharGetsNumbering(ch));
|
|
}
|
|
|
|
TRACEERRSZSC("HandleChar()", - _ecParseError);
|
|
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleEndOfPara()
|
|
*
|
|
* @mfunc
|
|
* Insert EOP and apply current paraformat
|
|
*
|
|
* @rdesc
|
|
* EC the error code
|
|
*/
|
|
EC CRTFRead::HandleEndOfPara()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleEndOfPara");
|
|
|
|
if(!_ped->_pdp->IsMultiLine()) // No EOPs permitted in single-
|
|
{ // line controls
|
|
Apply_PF(); // Apply any paragraph formatting
|
|
_ecParseError = ecTruncateAtCRLF; // Cause RTF reader to finish up
|
|
return ecTruncateAtCRLF;
|
|
}
|
|
|
|
Apply_CF(); // Apply _CF and save iCF, since
|
|
LONG iFormat = _prg->Get_iCF(); // it gets changed if processing
|
|
// CFE2_RUNISDBCS chars
|
|
EC ec;
|
|
|
|
if(IN_RANGE(tokenCell, _token, tokenNestCell) || _token == tokenRow)
|
|
ec = HandleChar((unsigned)CELL);
|
|
else
|
|
ec = _ped->fUseCRLF() // If RichEdit 1.0 compatibility
|
|
? HandleText(szaCRLF, ALL_ASCII) // mode, use CRLF; else CR or VT
|
|
: HandleChar((unsigned)(_token == tokenLineBreak ? VT :
|
|
_token == tokenPage ? FF : CR));
|
|
if(ec == ecNoError)
|
|
{
|
|
Apply_PF();
|
|
_cpThisPara = _prg->GetCp(); // New para starts after CRLF
|
|
}
|
|
_prg->Set_iCF(iFormat); // Restore iFormat if changed
|
|
ReleaseFormats(iFormat, -1); // Release iFormat (AddRef'd by
|
|
// Get_iCF())
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleText(szText, iAllASCII)
|
|
*
|
|
* @mfunc
|
|
* Handle the string of Ansi characters <p szText>
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleText(
|
|
BYTE * szText, //@parm string to be handled
|
|
int iAllASCII, //@parm enum indicating if string is all ASCII chars
|
|
LONG cchText) //@parm size of szText in bytes
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleText");
|
|
|
|
LONG cch;
|
|
BOOL fStateChng = FALSE;
|
|
WCHAR * pch;
|
|
STATE * pstate = _pstateStackTop;
|
|
TEXTFONT * ptf = pstate->ptf;
|
|
|
|
struct TEXTFONTSAVE : TEXTFONT
|
|
{
|
|
TEXTFONTSAVE(TEXTFONT *ptf)
|
|
{
|
|
if (ptf)
|
|
{
|
|
iCharRep = ptf->iCharRep;
|
|
sCodePage = ptf->sCodePage;
|
|
fCpgFromSystem = ptf->fCpgFromSystem;
|
|
}
|
|
}
|
|
};
|
|
|
|
BOOL fAdjustPtf = FALSE;
|
|
|
|
if(IN_RANGE(DEFFONT_LTRCH, pstate->iDefFont, DEFFONT_RTLCH))
|
|
{
|
|
// CharSet resolution based on directional control words
|
|
BOOL frtlch = pstate->iDefFont == DEFFONT_RTLCH;
|
|
if(_CF._iCharRep == DEFAULT_INDEX)
|
|
{
|
|
_CF._iCharRep = (BYTE)(frtlch ? _iCharRepBiDi : ANSI_INDEX);
|
|
_dwMaskCF |= CFM_CHARSET;
|
|
fAdjustPtf = TRUE;
|
|
}
|
|
else
|
|
{
|
|
BOOL fBiDiCharRep = IsRTLCharRep(_CF._iCharRep);
|
|
|
|
// If direction token doesn't correspond to current charset
|
|
if(fBiDiCharRep ^ frtlch)
|
|
{
|
|
_dwMaskCF |= CFM_CHARSET;
|
|
fAdjustPtf = TRUE;
|
|
if(!fBiDiCharRep) // Wasn't BiDi, but is now
|
|
SelectCurrentFont(_sDefaultBiDiFont);
|
|
_CF._iCharRep = (BYTE)(frtlch ? _iCharRepBiDi : ANSI_INDEX);
|
|
}
|
|
else if (fBiDiCharRep && ptf && !W32->IsBiDiCodePage(ptf->sCodePage))
|
|
fAdjustPtf = TRUE;
|
|
}
|
|
}
|
|
else if(_ped->IsBiDi() && _CF._iCharRep == DEFAULT_INDEX)
|
|
{
|
|
_CF._iCharRep = ANSI_INDEX;
|
|
_dwMaskCF |= CFM_CHARSET;
|
|
fAdjustPtf = TRUE;
|
|
}
|
|
if (fAdjustPtf && ptf)
|
|
{
|
|
ptf->sCodePage = (SHORT)CodePageFromCharRep(_CF._iCharRep);
|
|
pstate->SetCodePage(ptf->sCodePage);
|
|
}
|
|
|
|
TEXTFONTSAVE tfSave(ptf);
|
|
|
|
// TODO: what if szText cuts off in middle of DBCS?
|
|
|
|
if(!*szText)
|
|
goto CleanUp;
|
|
|
|
if (cchText != -1 && _cchUnicode < cchText)
|
|
{
|
|
// Re-allocate a bigger buffer
|
|
_szUnicode = (WCHAR *)PvReAlloc(_szUnicode, (cchText + 1) * sizeof(WCHAR));
|
|
if(!_szUnicode) // Re-allocate space for Unicode conversions
|
|
{
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
goto CleanUp;
|
|
}
|
|
_cchUnicode = cchText + 1;
|
|
}
|
|
|
|
if(iAllASCII == ALL_ASCII || pstate->nCodePage == CP_SYMBOL)
|
|
{
|
|
// Don't use MBTWC() in cases where text contains
|
|
// only ASCII chars (which don't require conversion)
|
|
for(cch = 0, pch = _szUnicode; *szText; cch++)
|
|
{
|
|
Assert(*szText <= 0x7F || pstate->nCodePage == CP_SYMBOL);
|
|
*pch++ = (WCHAR)*szText++;
|
|
}
|
|
*pch = 0;
|
|
|
|
_dwMaskCF2 |= CFM2_RUNISDBCS;
|
|
_CF._dwEffects &= ~CFE_RUNISDBCS;
|
|
|
|
// Fall through to AddText at end of HandleText()
|
|
}
|
|
else
|
|
{
|
|
BOOL fMissingCodePage;
|
|
|
|
// Run of text contains bytes > 0x7F.
|
|
// Ensure that we have the correct codepage with which to interpret
|
|
// these (possibly DBCS) bytes.
|
|
|
|
if(ptf && ptf->sCodePage == INVALID_CODEPAGE && !ptf->fCpgFromSystem)
|
|
{
|
|
if(_dwFlags & SF_USECODEPAGE)
|
|
{
|
|
_CF._iCharRep = CharRepFromCodePage(_nCodePage);
|
|
_dwMaskCF |= CFM_CHARSET;
|
|
}
|
|
|
|
// Determine codepage from the font name
|
|
else if(CpgInfoFromFaceName(pstate->ptf))
|
|
{
|
|
fStateChng = TRUE;
|
|
SelectCurrentFont(pstate->ptf->sHandle);
|
|
Assert(ptf->sCodePage != INVALID_CODEPAGE && ptf->fCpgFromSystem);
|
|
}
|
|
else
|
|
{
|
|
// Here, we were not able to determine a cpg/charset value
|
|
// from the font name. We have two choices: (1) either choose
|
|
// some fallback value like 1252/0 or (2) rely on the
|
|
// document-level cpg value.
|
|
//
|
|
// I think choosing the document-level cpg value will give
|
|
// us the best results. In FE cases, it will likely err
|
|
// on the side of tagging too many runs as CFE2_RUNISDBCS, but
|
|
// that is safer than using a western cpg and potentially missing
|
|
// runs which should be CFE2_RUNISDBCS.
|
|
}
|
|
}
|
|
|
|
Assert(!IsUTF8 || pstate->nCodePage == CP_UTF8);
|
|
|
|
if (pstate->nCodePage == INVALID_CODEPAGE && ptf)
|
|
pstate->nCodePage = ptf->sCodePage;
|
|
|
|
cch = MBTWC(pstate->nCodePage, 0,
|
|
(char *)szText, -1,
|
|
_szUnicode, _cchUnicode, &fMissingCodePage);
|
|
|
|
AssertSz(cch > 0, "CRTFRead::HandleText(): MBTWC implementation changed"
|
|
" such that it returned a value <= 0");
|
|
|
|
if(!fMissingCodePage || !W32->IsFECodePage(pstate->nCodePage))
|
|
{
|
|
// Use result of MBTWC if:
|
|
// (1) we converted some chars and did the conversion with the codepage
|
|
// provided.
|
|
// (2) we converted some chars but couldn't use the codepage provided,
|
|
// but the codepage is invalid. Since the codepage is invalid,
|
|
// we can't do anything more sophisticated with the text before
|
|
// adding to the backing store
|
|
|
|
cch--; // don't want char count to including terminating NULL
|
|
|
|
_dwMaskCF2 |= CFM2_RUNISDBCS;
|
|
_CF._dwEffects &= ~CFE_RUNISDBCS;
|
|
if(pstate->nCodePage == INVALID_CODEPAGE)
|
|
_CF._dwEffects |= CFE_RUNISDBCS;
|
|
|
|
// fall through to AddText at end of HandleText()
|
|
}
|
|
else
|
|
{
|
|
// Conversion to Unicode failed. Break up the string of
|
|
// text into runs of ASCII and non-ASCII characters.
|
|
|
|
// FUTURE(BradO): Here, I am saving dwMask and restoring it before
|
|
// each AddText. I'm not sure this is neccessary. When I have
|
|
// the time, I should revisit this save/restoring and
|
|
// determine that it is indeed neccessary.
|
|
|
|
BOOL fPrevIsASCII = ((*szText <= 0x7F) ? TRUE : FALSE);
|
|
BOOL fCurrentIsASCII = FALSE;
|
|
BOOL fLastChunk = FALSE;
|
|
DWORD dwMaskSave = _dwMaskCF;
|
|
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
|
|
CCharFormat CFSave = _CF;
|
|
#endif
|
|
|
|
pch = _szUnicode;
|
|
cch = 0;
|
|
|
|
// (!*szText && *pch) is the case where we do the AddText for the
|
|
// last chunk of text
|
|
while(*szText || fLastChunk)
|
|
{
|
|
// fCurrentIsASCII assumes that no byte <= 0x7F is a
|
|
// DBCS lead-byte
|
|
if(fLastChunk ||
|
|
(fPrevIsASCII != (fCurrentIsASCII = (*szText <= 0x7F))))
|
|
{
|
|
_dwMaskCF = dwMaskSave;
|
|
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
|
|
_CF = CFSave;
|
|
#endif
|
|
*pch = 0;
|
|
|
|
_dwMaskCF2 |= CFM2_RUNISDBCS;
|
|
_CF._dwEffects |= CFE_RUNISDBCS;
|
|
if(fPrevIsASCII)
|
|
_CF._dwEffects &= ~CFE_RUNISDBCS;
|
|
|
|
Assert(cch);
|
|
pch = _szUnicode;
|
|
|
|
AddText(pch, cch, TRUE);
|
|
|
|
cch = 0;
|
|
fPrevIsASCII = fCurrentIsASCII;
|
|
|
|
// My assumption in saving _dwMaskCF is that the remainder
|
|
// of the _CF is unchanged by AddText. This assert verifies
|
|
// this assumption.
|
|
AssertSz(!CompareMemory(&CFSave._iCharRep, &_CF._iCharRep,
|
|
sizeof(CCharFormat) - sizeof(DWORD)) &&
|
|
!((CFSave._dwEffects ^ _CF._dwEffects) & ~CFE_RUNISDBCS),
|
|
"CRTFRead::HandleText(): AddText has been changed "
|
|
"and now alters the _CF structure.");
|
|
|
|
if(fLastChunk) // Last chunk of text was AddText'd
|
|
break;
|
|
}
|
|
|
|
// Not the last chunk of text.
|
|
Assert(*szText);
|
|
|
|
// Advance szText pointer
|
|
if (!fCurrentIsASCII && *(szText + 1) &&
|
|
GetTrailBytesCount(*szText, pstate->nCodePage))
|
|
{
|
|
// Current byte is a lead-byte of a DBCS character
|
|
*pch++ = *szText++;
|
|
++cch;
|
|
}
|
|
*pch++ = *szText++;
|
|
++cch;
|
|
|
|
// Must do an AddText for the last chunk of text
|
|
if(!*szText || cch >= _cchUnicode - 1)
|
|
fLastChunk = TRUE;
|
|
}
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
if(cch > 0)
|
|
{
|
|
if(!_cCell || _iCell < _cCell)
|
|
AddText(_szUnicode, cch, TRUE);
|
|
if(fStateChng && ptf)
|
|
{
|
|
ptf->iCharRep = tfSave.iCharRep;
|
|
ptf->sCodePage = tfSave.sCodePage;
|
|
ptf->fCpgFromSystem = tfSave.fCpgFromSystem;
|
|
SelectCurrentFont(ptf->sHandle);
|
|
}
|
|
}
|
|
|
|
CleanUp:
|
|
TRACEERRSZSC("HandleText()", - _ecParseError);
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleUN(pstate)
|
|
*
|
|
* @mfunc
|
|
* Handle run of Unicode characters given by \uN control words
|
|
*/
|
|
void CRTFRead::HandleUN(
|
|
STATE *pstate)
|
|
{
|
|
char ach = 0;
|
|
LONG cch = 0;
|
|
DWORD ch; // Treat as unsigned quantity
|
|
WCHAR * pch = _szUnicode;
|
|
|
|
_dwMaskCF2 |= CFM2_RUNISDBCS;
|
|
_CF._dwEffects &= ~CFE_RUNISDBCS;
|
|
|
|
do
|
|
{
|
|
if(_iParam < 0)
|
|
_iParam = (WORD)_iParam;
|
|
ch = _iParam;
|
|
if(pstate->ptf && pstate->ptf->iCharRep == SYMBOL_INDEX)
|
|
{
|
|
if(IN_RANGE(0xF000, ch, 0xF0FF))// Compensate for converters
|
|
ch -= 0xF000; // that write symbol codes
|
|
// up high
|
|
else if(ch > 255) // Whoops, RTF is using con-
|
|
{ // verted value for symbol:
|
|
char ach; // convert back
|
|
WCHAR wch = ch; // Avoid endian problems
|
|
WCTMB(1252, 0, &wch, 1, &ach, 1, NULL, NULL, NULL);
|
|
ch = (BYTE)ach; // Review: use CP_ACP??
|
|
}
|
|
}
|
|
if(IN_RANGE(0x10000, ch, 0x10FFFF)) // Higher-plane char:
|
|
{ // Store as Unicode surrogate
|
|
ch -= 0x10000; // pair
|
|
*pch++ = 0xD800 + (ch >> 10);
|
|
ch = 0xDC00 + (ch & 0x3FF);
|
|
cch++;
|
|
}
|
|
if(!IN_RANGE(STARTFIELD, ch, NOTACHAR) && // Don't insert 0xFFF9 - 0xFFFF
|
|
(!IN_RANGE(0xDC00, ch, 0xDFFF) || // or lone trail surrogate
|
|
IN_RANGE(0xD800, cch ? *(pch - 1) : _prg->GetPrevChar(), 0xDBFF)))
|
|
{
|
|
*pch++ = ch;
|
|
cch++;
|
|
}
|
|
for(LONG i = pstate->cbSkipForUnicodeMax; i--; )
|
|
GetCharEx(); // Eat the \uc N chars
|
|
ach = GetChar(); // Get next char
|
|
while(IsASCIIEOP(ach))
|
|
{
|
|
ach = GetChar();
|
|
if (_ecParseError != ecNoError)
|
|
return;
|
|
}
|
|
if(ach != BSLASH) // Not followed by \, so
|
|
{ // not by \uN either
|
|
UngetChar(); // Put back BSLASH
|
|
break;
|
|
}
|
|
ach = GetChar();
|
|
if((ach | ' ') != 'u')
|
|
{
|
|
UngetChar(2); // Not \u; put back \x
|
|
break;
|
|
}
|
|
GetParam(GetChar()); // \u so try for _iParam = N
|
|
if(!_fParam) // No \uN; put back \u
|
|
{
|
|
UngetChar(2);
|
|
break;
|
|
}
|
|
} while(cch < _cchUnicode - 2 && _iParam);
|
|
|
|
AddText(_szUnicode, cch, TRUE, TRUE);
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleNumber()
|
|
*
|
|
* @mfunc
|
|
* Insert the number _iParam as text. Useful as a workaround for
|
|
* Word RTF that leaves out the blank between \ltrch and a number.
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleNumber()
|
|
{
|
|
if(!_fParam) // Nothing to do
|
|
return _ecParseError;
|
|
|
|
LONG n = _iParam;
|
|
BYTE *pch = _szText;
|
|
|
|
if(n < 0)
|
|
{
|
|
n = -n;
|
|
*pch++ = '-';
|
|
}
|
|
for(LONG d = 1; d < n; d *= 10) // d = smallest power of 10 > n
|
|
;
|
|
if(d > n)
|
|
d /= 10; // d = largest power of 10 < n
|
|
|
|
while(d)
|
|
{
|
|
*pch++ = (WORD)(n/d + '0'); // Store digit
|
|
n = n % d; // Setup remainder
|
|
d /= 10;
|
|
}
|
|
*pch = 0;
|
|
_token = tokenASCIIText;
|
|
HandleTextToken(_pstateStackTop);
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleTextToken(pstate)
|
|
*
|
|
* @mfunc
|
|
* Handle tokenText
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*/
|
|
EC CRTFRead::HandleTextToken(
|
|
STATE *pstate)
|
|
{
|
|
COLORREF *pclrf;
|
|
|
|
switch (pstate->sDest)
|
|
{
|
|
case destColorTable:
|
|
pclrf = _colors.Add(1, NULL);
|
|
if(!pclrf)
|
|
goto OutOfRAM;
|
|
|
|
*pclrf = _fGetColorYet ?
|
|
RGB(pstate->bRed, pstate->bGreen, pstate->bBlue) : tomAutoColor;
|
|
|
|
// Prepare for next color table entry
|
|
pstate->bRed =
|
|
pstate->bGreen =
|
|
pstate->bBlue = 0;
|
|
_fGetColorYet = FALSE; // in case more "empty" color
|
|
break;
|
|
|
|
case destFontTable:
|
|
if(!pstate->fRealFontName)
|
|
{
|
|
ReadFontName(pstate, _token == tokenASCIIText
|
|
? ALL_ASCII : CONTAINS_NONASCII);
|
|
}
|
|
break;
|
|
|
|
case destRealFontName:
|
|
{
|
|
STATE * const pstatePrev = pstate->pstatePrev;
|
|
|
|
if(pstatePrev && pstatePrev->sDest == destFontTable)
|
|
{
|
|
// Mark previous state so that tagged font name will be ignored
|
|
// AROO: Do this before calling ReadFontName so that
|
|
// AROO: it doesn't try to match font name
|
|
pstatePrev->fRealFontName = TRUE;
|
|
ReadFontName(pstatePrev,
|
|
_token == tokenASCIIText ? ALL_ASCII : CONTAINS_NONASCII);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case destFieldInstruction:
|
|
HandleFieldInstruction();
|
|
break;
|
|
|
|
case destObjectClass:
|
|
if(StrAlloc(&_prtfObject->szClass, _szText))
|
|
goto OutOfRAM;
|
|
break;
|
|
|
|
case destObjectName:
|
|
if(StrAlloc(&_prtfObject->szName, _szText))
|
|
goto OutOfRAM;
|
|
break;
|
|
|
|
case destStyleSheet:
|
|
// _szText has style name, e.g., "heading 1"
|
|
if(W32->ASCIICompareI(_szText, (unsigned char *)"heading", 7))
|
|
{
|
|
DWORD dwT = (unsigned)(_szText[8] - '0');
|
|
if(dwT < NSTYLES)
|
|
_rgStyles[dwT] = (BYTE)_Style;
|
|
}
|
|
break;
|
|
|
|
case destShapeName:
|
|
if(pstate->fBackground)
|
|
{
|
|
TOKEN token = TokenFindKeyword(_szText, rgShapeKeyword, cShapeKeywords);
|
|
_bShapeNameIndex = (token != tokenUnknownKeyword) ? (BYTE)token : 0;
|
|
}
|
|
break;
|
|
|
|
case destShapeValue:
|
|
if(_bShapeNameIndex)
|
|
{
|
|
CDocInfo *pDocInfo = _ped->GetDocInfoNC();
|
|
BYTE *pch = _szText;
|
|
BOOL fNegative = FALSE;
|
|
|
|
if(*pch == '-')
|
|
{
|
|
fNegative = TRUE;
|
|
pch++;
|
|
}
|
|
for(_iParam = 0; IsDigit(*pch); pch++)
|
|
_iParam = _iParam*10 + *pch - '0';
|
|
|
|
if(fNegative)
|
|
_iParam = -_iParam;
|
|
|
|
switch(_bShapeNameIndex)
|
|
{
|
|
case shapeFillBackColor:
|
|
case shapeFillColor:
|
|
{
|
|
if(_iParam > 0xFFFFFF)
|
|
_iParam = 0;
|
|
if(_bShapeNameIndex == shapeFillColor)
|
|
pDocInfo->_crColor = (COLORREF)_iParam;
|
|
else
|
|
pDocInfo->_crBackColor = (COLORREF)_iParam;
|
|
if(pDocInfo->_nFillType == -1)
|
|
pDocInfo->_nFillType = 0;
|
|
break;
|
|
}
|
|
case shapeFillType:
|
|
// DEBUGGG: more general _nFillType commented out for now
|
|
// pDocInfo->_nFillType = _iParam;
|
|
if(pDocInfo->_nFillType)
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
pDocInfo->_crColor = RGB(255, 255, 255);
|
|
pDocInfo->_crBackColor = RGB(255, 255, 255);
|
|
break;
|
|
|
|
case shapeFillAngle:
|
|
pDocInfo->_sFillAngle = HIWORD(_iParam);
|
|
break;
|
|
|
|
case shapeFillFocus:
|
|
pDocInfo->_bFillFocus = _iParam;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case destDocumentArea:
|
|
case destFollowingPunct:
|
|
case destLeadingPunct:
|
|
break;
|
|
|
|
// This has been changed now. We will store the Punct strings as
|
|
// raw text strings. So, we don't have to convert them.
|
|
// This code is keep here just in case we want to change back.
|
|
#if 0
|
|
case destDocumentArea:
|
|
if (_tokenLast != tokenFollowingPunct &&
|
|
_tokenLast != tokenLeadingPunct)
|
|
{
|
|
break;
|
|
} // Else fall thru to
|
|
// destFollowingPunct
|
|
case destFollowingPunct:
|
|
case destLeadingPunct:
|
|
// TODO(BradO): Consider some kind of merging heuristic when
|
|
// we paste FE RTF (for lead and follow chars, that is).
|
|
if(!(_dwFlags & SFF_SELECTION))
|
|
{
|
|
int cwch = MBTWC(INVALID_CODEPAGE, 0,
|
|
(char *)_szText, -1,
|
|
NULL, 0,
|
|
NULL);
|
|
Assert(cwch);
|
|
WCHAR *pwchBuf = (WCHAR *)PvAlloc(cwch * sizeof(WCHAR), GMEM_ZEROINIT);
|
|
|
|
if(!pwchBuf)
|
|
goto OutOfRAM;
|
|
|
|
SideAssert(MBTWC(INVALID_CODEPAGE, 0,
|
|
(char *)_szText, -1,
|
|
pwchBuf, cwch,
|
|
NULL) > 0);
|
|
|
|
if(pstate->sDest == destFollowingPunct)
|
|
_ped->SetFollowingPunct(pwchBuf);
|
|
else
|
|
{
|
|
Assert(pstate->sDest == destLeadingPunct);
|
|
_ped->SetLeadingPunct(pwchBuf);
|
|
}
|
|
FreePv(pwchBuf);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
if(!IsLowMergedCell())
|
|
HandleText(_szText, _token == tokenASCIIText ? ALL_ASCII : CONTAINS_NONASCII);
|
|
}
|
|
return _ecParseError;
|
|
|
|
OutOfRAM:
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::AddText(pch, cch, fNumber, fUN)
|
|
*
|
|
* @mfunc
|
|
* Add <p cch> chars of the string <p pch> to the range _prg
|
|
*
|
|
* @rdesc
|
|
* error code placed in _ecParseError
|
|
*/
|
|
EC CRTFRead::AddText(
|
|
WCHAR * pch, //@parm Text to add
|
|
LONG cch, //@parm Count of chars to add
|
|
BOOL fNumber, //@parm Indicates whether or not to prepend numbering
|
|
BOOL fUN) //@parm TRUE means caller is \uN handler
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::AddText");
|
|
|
|
LONG cchAdded;
|
|
LONG cchT;
|
|
STATE * const pstate = _pstateStackTop;
|
|
WCHAR * szDest;
|
|
LONG cchMove;
|
|
|
|
// AROO: No saving state before this point (other than pstate)
|
|
// AROO: This would screw recursion below
|
|
|
|
//AssertSz(pstate, "CRTFRead::AddText: no state");
|
|
|
|
if((DWORD)cch > _cchMax)
|
|
{
|
|
cch = (LONG)_cchMax;
|
|
_ecParseError = ecTextMax;
|
|
if(*pch == STARTFIELD)
|
|
return ecTextMax; // Don't enter partial startfield
|
|
}
|
|
|
|
if(!cch)
|
|
return _ecParseError;
|
|
|
|
if(_fStartRow)
|
|
DelimitRow(szRowStart);
|
|
|
|
// FUTURE(BradO): This strategy for \pntext is prone to bugs, I believe.
|
|
// The recursive call to AddText to add the \pntext will trounce the
|
|
// accumulated _CF diffs associated with the text for which AddText is
|
|
// called. I believe we should save and restore _CF before and after
|
|
// the recursive call to AddText below. Also, it isn't sufficient to
|
|
// accumulate bits of \pntext as below, since each bit might be formatted
|
|
// with different _CF properties. Instead, we should accumulate a mini-doc
|
|
// complete with multiple text, char and para runs (or some stripped down
|
|
// version of this strategy).
|
|
|
|
if(pstate && pstate->sDest == destParaNumText && pch != szRowStart)
|
|
{
|
|
szDest = _szNumText + _cchUsedNumText;
|
|
cch = min(cch, cchMaxNumText - 1 - _cchUsedNumText);
|
|
if(cch > 0)
|
|
{
|
|
MoveMemory((BYTE *)szDest, (BYTE *)pch, cch*2);
|
|
szDest[cch] = TEXT('\0'); // HandleText() takes sz
|
|
_cchUsedNumText += cch;
|
|
}
|
|
return ecNoError;
|
|
}
|
|
|
|
if(pstate && _cchUsedNumText && fNumber) // Some \pntext available
|
|
{
|
|
// Bug 3496 - The fNumber flag is an ugly hack to work around RTF
|
|
// commonly written by Word. Often, to separate a numbered list
|
|
// by page breaks, Word will write:
|
|
// <NUMBERING INFO> \page <PARAGRAPH TEXT>
|
|
// The paragraph numbering should precede the paragraph text rather
|
|
// than the page break. The fNumber flag is set to FALSE when the
|
|
// the text being added should not be prepended with the para-numbering,
|
|
// as is the case with \page (mapped to FF).
|
|
|
|
cchT = _cchUsedNumText;
|
|
_cchUsedNumText = 0; // Prevent infinite recursion
|
|
|
|
if(!pstate->fBullet)
|
|
{
|
|
// If there are any _CF diffs to be injected, they will be trounced
|
|
// by this recursive call (see FUTURE comment above).
|
|
|
|
// Since we didn't save _CF data from calls to AddText with
|
|
// pstate->sDest == destParaNumText, we have no way of setting up
|
|
// CFE2_RUNISDBCS and CFM2_RUNISDBCS (see FUTURE comment above).
|
|
|
|
AddText(_szNumText, cchT, FALSE);
|
|
}
|
|
else if(_PF.IsListNumbered() && _szNumText[cchT - 1] == TAB)
|
|
{
|
|
AssertSz(cchT >= 1, "Invalid numbered text count");
|
|
|
|
if (cchT > 1)
|
|
{
|
|
WCHAR ch = _szNumText[cchT - 2];
|
|
|
|
_wNumberingStyle = (_wNumberingStyle & ~0x300)
|
|
| (ch == '.' ? PFNS_PERIOD :
|
|
ch != ')' ? PFNS_PLAIN :
|
|
_szNumText[0] == '(' ? PFNS_PARENS : PFNS_PAREN);
|
|
}
|
|
else
|
|
{
|
|
// There is only a tab so we will assume they meant to
|
|
// skip numbering.
|
|
_wNumberingStyle = PFNS_NONUMBER;
|
|
}
|
|
}
|
|
}
|
|
|
|
Apply_CF(); // Apply formatting changes in _CF
|
|
|
|
// CTxtRange::ReplaceRange will change the character formatting
|
|
// and possibly adjust the _rpCF forward if the current char
|
|
// formatting includes protection. The changes affected by
|
|
// CTxtRange::ReplaceRange are necessary only for non-streaming
|
|
// input, so we save state before and restore it after the call
|
|
// to CTxtRange::ReplaceRange
|
|
|
|
LONG iFormatSave = _prg->Get_iCF(); // Save state
|
|
QWORD qwFlags = GetCharFlags(pch, cch);
|
|
|
|
if(fUN && // \uN generated string
|
|
(!pstate->ptf || pstate->ptf->sCodePage == INVALID_CODEPAGE || qwFlags & FOTHER ||
|
|
(qwFlags & GetFontSignatureFromFace(_ped->GetCharFormat(iFormatSave)->_iFont)) != qwFlags &&
|
|
(!(qwFlags & (FARABIC | FHEBREW)) || _fNon0CharSet)))
|
|
{
|
|
// No charset info for \uN or current charset doesn't support char
|
|
cchAdded = _prg->CleanseAndReplaceRange(cch, pch, FALSE, NULL, pch);
|
|
}
|
|
else
|
|
{
|
|
cchAdded = _prg->ReplaceRange(cch, pch, NULL, SELRR_IGNORE, &cchMove,
|
|
RR_NO_LP_CHECK);
|
|
|
|
for(cchT = cch - 1; cchT; cchT--)
|
|
qwFlags |= GetCharFlags(++pch, cchT);// Note if ComplexScript
|
|
|
|
_ped->OrCharFlags(qwFlags);
|
|
}
|
|
_fBody = TRUE;
|
|
_prg->Set_iCF(iFormatSave); // Restore state
|
|
ReleaseFormats(iFormatSave, -1);
|
|
Assert(!_prg->GetCch());
|
|
|
|
if(cchAdded != cch)
|
|
{
|
|
Tracef(TRCSEVERR, "AddText(): Only added %d out of %d", cchAdded, cch);
|
|
_ecParseError = ecGeneralFailure;
|
|
if(cchAdded <= 0)
|
|
return _ecParseError;
|
|
}
|
|
_cchMax -= cchAdded;
|
|
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::Apply_CF()
|
|
*
|
|
* @mfunc
|
|
* Apply character formatting changes collected in _CF
|
|
*/
|
|
void CRTFRead::Apply_CF()
|
|
{
|
|
// If any CF changes, update range's _iFormat
|
|
if(_dwMaskCF || _dwMaskCF2)
|
|
{
|
|
AssertSz(_prg->GetCch() == 0,
|
|
"CRTFRead::Apply_CF: nondegenerate range");
|
|
|
|
_prg->SetCharFormat(&_CF, 0, NULL, _dwMaskCF, _dwMaskCF2);
|
|
_dwMaskCF = 0;
|
|
_dwMaskCF2 = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::Apply_PF()
|
|
*
|
|
* @mfunc
|
|
* Apply paragraph format given by _PF
|
|
*
|
|
* @rdesc
|
|
* if table row delimiter with nonzero cell count, PF::_iTabs; else -1
|
|
*/
|
|
SHORT CRTFRead::Apply_PF()
|
|
{
|
|
LONG cp = _prg->GetCp();
|
|
DWORD dwMask = _dwMaskPF;
|
|
DWORD dwMask2 = _dwMaskPF2;
|
|
SHORT iTabs = -1;
|
|
CParaFormat *pPF = &_PF;
|
|
|
|
if(_pstateStackTop)
|
|
{
|
|
Assert(_pstateStackTop->pPF);
|
|
|
|
// Add PF diffs to *_pstateStackTop->pPF
|
|
if(!_pstateStackTop->AddPF(_PF, _bDocType, _dwMaskPF, _dwMaskPF2))
|
|
{
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
return -1;
|
|
}
|
|
_dwMaskPF = _dwMaskPF2 = 0; // _PF contains delta's from *_pstateStackTop->pPF
|
|
|
|
pPF = _pstateStackTop->pPF;
|
|
dwMask = _pstateStackTop->dwMaskPF;
|
|
Assert(dwMask == PFM_ALLRTF);
|
|
if(pPF->_wNumbering)
|
|
{
|
|
pPF->_wNumberingTab = _pstateStackTop->sIndentNumbering;
|
|
pPF->_wNumberingStyle = _wNumberingStyle;
|
|
}
|
|
|
|
if(_bTableLevelIP + _bTableLevel)
|
|
{
|
|
pPF->_wEffects |= PFE_TABLE;
|
|
dwMask |= PFM_TABLE;
|
|
pPF->_bTableLevel = min(_bTableLevel + _bTableLevelIP, MAXTABLENEST);
|
|
}
|
|
}
|
|
if(dwMask & PFM_TABSTOPS)
|
|
{
|
|
LONG cTab = _cTab;
|
|
BOOL fIsTableRowDelimiter = pPF->IsTableRowDelimiter();
|
|
const LONG *prgxTabs = &_rgxCell[0];
|
|
|
|
if(fIsTableRowDelimiter)
|
|
{
|
|
dwMask2 = PFM2_ALLOWTRDCHANGE;
|
|
if(!_cCell)
|
|
{
|
|
if(_iTabsTable >= 0) // No cells defined here;
|
|
{ // Use previous table defs
|
|
Assert(_bTableLevel == 1 && !_fNo_iTabsTable);
|
|
CTabs *pTabs = GetTabsCache()->Elem(_iTabsTable);
|
|
_cCell = pTabs->_cTab/(CELL_EXTRA + 1);
|
|
prgxTabs = pTabs->_prgxTabs;
|
|
}
|
|
else if(_prg->_rpTX.IsAfterTRD(ENDFIELD) && _iCell)
|
|
{
|
|
LONG x = 2000; // Bad RTF: no \cellx's. Def 'em
|
|
for(LONG i = 1; i <= _iCell; i++)
|
|
{
|
|
HandleCellx(x);
|
|
x += 2000;
|
|
}
|
|
}
|
|
}
|
|
cTab = _cCell;
|
|
}
|
|
// Caching a tabs array AddRefs the corresponding cached tabs entry.
|
|
// Be absolutely sure to release the entry before exiting the routine
|
|
// that caches it (see GetTabsCache()->Release at end of this function).
|
|
pPF->_bTabCount = cTab;
|
|
if(fIsTableRowDelimiter)
|
|
cTab *= CELL_EXTRA + 1;
|
|
pPF->_iTabs = GetTabsCache()->Cache(prgxTabs, cTab);
|
|
if(fIsTableRowDelimiter && _bTableLevel == 1)
|
|
{
|
|
iTabs = pPF->_iTabs;
|
|
if(!_fNo_iTabsTable)
|
|
_iTabsTable = pPF->_iTabs;
|
|
}
|
|
AssertSz(!cTab || pPF->_iTabs >= 0,
|
|
"CRTFRead::Apply_PF: illegal pPF->_iTabs");
|
|
}
|
|
|
|
LONG fCell = (_prg->GetPrevChar() == CELL);
|
|
LONG fIsAfterTRD = _prg->_rpTX.IsAfterTRD(0);
|
|
|
|
if(fCell || fIsAfterTRD) // Deal with table delimiters
|
|
{ // in hidden text and with
|
|
_prg->_rpCF.AdjustBackward(); // custom colors
|
|
if(_prg->IsHidden())
|
|
{ // Turn off hidden text
|
|
CCharFormat CF;
|
|
CF._dwEffects = 0;
|
|
|
|
_prg->Set(cp, fCell ? 1 : 2);
|
|
_prg->SetCharFormat(&CF, 0, NULL, CFM_HIDDEN, 0);
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
_CF._dwEffects |= CFE_HIDDEN; // Restore CFE_HIDDEN
|
|
_dwMaskCF |= CFM_HIDDEN;
|
|
}
|
|
_prg->_rpCF.AdjustForward();
|
|
if(fIsAfterTRD && _crCellCustom1)
|
|
{
|
|
pPF->_crCustom1 = _crCellCustom1;
|
|
dwMask |= PFM_SHADING;
|
|
if(_crCellCustom2)
|
|
{
|
|
pPF->_crCustom2 = _crCellCustom2;
|
|
dwMask |= PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dwMask)
|
|
{
|
|
_prg->Set(cp, cp - _cpThisPara); // Select back to _cpThisPara
|
|
_prg->SetParaFormat(pPF, NULL, dwMask, dwMask2);
|
|
}
|
|
_prg->Set(cp, 0); // Restore _prg to an IP
|
|
GetTabsCache()->Release(pPF->_iTabs);
|
|
pPF->_iTabs = -1;
|
|
return iTabs;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::SetBorderParm(&Parm, Value)
|
|
*
|
|
* @mfunc
|
|
* Set the border pen width in half points for the current border
|
|
* (_bBorder)
|
|
*/
|
|
void CRTFRead::SetBorderParm(
|
|
WORD& Parm,
|
|
LONG Value)
|
|
{
|
|
Assert(_bBorder <= 3);
|
|
|
|
Value = min(Value, 15);
|
|
Value = max(Value, 0);
|
|
Parm &= ~(0xF << 4*_bBorder);
|
|
Parm |= Value << 4*_bBorder;
|
|
_dwMaskPF |= PFM_BORDER;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::HandleToken()
|
|
*
|
|
* @mfunc
|
|
* Grand switch board that handles all tokens. Switches on _token
|
|
*
|
|
* @rdesc
|
|
* EC The error code
|
|
*
|
|
* @comm
|
|
* Token values are chosen contiguously (see tokens.h and tokens.c) to
|
|
* encourage the compiler to use a jump table. The lite-RTF keywords
|
|
* come first, so that an optimized OLE-free version works well. Some
|
|
* groups of keyword tokens are ordered so as to simplify the code, e.g,
|
|
* those for font family names, CF effects, and paragraph alignment.
|
|
*/
|
|
EC CRTFRead::HandleToken()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::HandleToken");
|
|
|
|
DWORD dwT; // Temporary DWORD
|
|
LONG dy, i;
|
|
LONG iParam = _iParam;
|
|
const CCharFormat * pCF;
|
|
STATE * pstate = _pstateStackTop;
|
|
TEXTFONT * ptf;
|
|
WORD wT; // Temporary WORD
|
|
|
|
if(!pstate && _token != tokenStartGroup ||
|
|
IN_RANGE(tokenPicFirst, _token, tokenObjLast) && !_prtfObject)
|
|
{
|
|
abort: _ecParseError = ecAbort;
|
|
return ecAbort;
|
|
}
|
|
switch (_token)
|
|
{
|
|
case tokenURtf: // \urtf N - Preferred RE format
|
|
PARSERCOVERAGE_CASE(); // Currently we ignore the N
|
|
_dwFlags &= 0xFFFF; // Kill possible codepage
|
|
_dwFlags |= SF_USECODEPAGE | (CP_UTF8 << 16); // Save bit for Asserts
|
|
pstate->SetCodePage(CP_UTF8);
|
|
goto rtf;
|
|
|
|
case tokenPocketWord: // \pwd N - Pocket Word
|
|
_dwFlags |= SFF_PWD;
|
|
|
|
case tokenRtf: // \rtf N - Backward compatible
|
|
PARSERCOVERAGE_CASE();
|
|
rtf: if(pstate->pstatePrev)
|
|
goto abort; // Can't handle nested rtf
|
|
pstate->sDest = destRTF;
|
|
Assert(pstate->nCodePage == INVALID_CODEPAGE ||
|
|
pstate->nCodePage == (int)(_dwFlags >> 16) &&
|
|
(_dwFlags & SF_USECODEPAGE));
|
|
|
|
if(!_fonts.Count() && !_fonts.Add(1, NULL)) // If can't add a font,
|
|
goto OutOfRAM; // report the bad news
|
|
_sDefaultFont = 0; // Set up valid default font
|
|
ptf = _fonts.Elem(0);
|
|
pstate->ptf = ptf; // Get char set, pitch, family
|
|
pCF = _prg->GetCF(); // from current range font
|
|
ptf->iCharRep = pCF->_iCharRep; // These are guaranteed OK
|
|
ptf->bPitchAndFamily = pCF->_bPitchAndFamily;
|
|
ptf->sCodePage = (SHORT)CodePageFromCharRep(pCF->_iCharRep);
|
|
wcscpy(ptf->szName, GetFontName(pCF->_iFont));
|
|
ptf->fNameIsDBCS = (pCF->_dwEffects & CFE_FACENAMEISDBCS) != 0;
|
|
pstate->cbSkipForUnicodeMax = iUnicodeCChDefault;
|
|
break;
|
|
|
|
case tokenViewKind: // \viewkind N
|
|
if(!(_dwFlags & SFF_SELECTION) && IsUTF8)// RTF applies to document:
|
|
_ped->SetViewKind(iParam); // For now, only do for \urtf
|
|
break; // (need to work some more
|
|
// on OutlineView)
|
|
case tokenViewScale: // \viewscale N
|
|
if(_dwFlags & SFF_PERSISTVIEWSCALE &&
|
|
!(_dwFlags & SFF_SELECTION)) // RTF applies to document:
|
|
_ped->SetViewScale(iParam);
|
|
break;
|
|
|
|
case tokenCharacterDefault: // \plain
|
|
PARSERCOVERAGE_CASE();
|
|
SetPlain(pstate);
|
|
break;
|
|
|
|
case tokenCharSetAnsi: // \ansi
|
|
PARSERCOVERAGE_CASE();
|
|
_iCharRep = ANSI_INDEX;
|
|
break;
|
|
|
|
case tokenMac: // \mac
|
|
_fMac = TRUE;
|
|
break;
|
|
|
|
case tokenDefaultLanguage: // \deflang
|
|
PARSERCOVERAGE_CASE();
|
|
_sDefaultLanguage = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenDefaultLanguageFE: // \deflangfe
|
|
PARSERCOVERAGE_CASE();
|
|
_sDefaultLanguageFE = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenDefaultTabWidth: // \deftab
|
|
PARSERCOVERAGE_CASE();
|
|
_sDefaultTabWidth = (SHORT)iParam;
|
|
break;
|
|
|
|
|
|
//--------------------------- Font Control Words -------------------------------
|
|
|
|
case tokenDefaultFont: // \deff N
|
|
PARSERCOVERAGE_CASE();
|
|
if(iParam >= 0)
|
|
{
|
|
if(!_fonts.Count() && !_fonts.Add(1, NULL)) // If can't add a font,
|
|
goto OutOfRAM; // report the bad news
|
|
_fonts.Elem(0)->sHandle = _sDefaultFont = (SHORT)iParam;
|
|
}
|
|
TRACEERRSZSC("tokenDefaultFont: Negative value", iParam);
|
|
break;
|
|
|
|
case tokenDefaultBiDiFont: // \adeff N
|
|
PARSERCOVERAGE_CASE();
|
|
if(iParam >=0 && _fonts.Count() == 1)
|
|
{
|
|
if(!_fonts.Add(1, NULL))
|
|
goto OutOfRAM;
|
|
_fonts.Elem(1)->sHandle = _sDefaultBiDiFont = (SHORT)iParam;
|
|
}
|
|
TRACEERRSZSC("tokenDefaultBiDiFont: Negative value", iParam);
|
|
break;
|
|
|
|
case tokenFontTable: // \fonttbl
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destFontTable;
|
|
pstate->ptf = NULL;
|
|
break;
|
|
|
|
case tokenFontFamilyBidi: // \fbidi
|
|
case tokenFontFamilyTechnical: // \ftech
|
|
case tokenFontFamilyDecorative: // \fdecor
|
|
case tokenFontFamilyScript: // \fscript
|
|
case tokenFontFamilyModern: // \fmodern
|
|
case tokenFontFamilySwiss: // \fswiss
|
|
case tokenFontFamilyRoman: // \froman
|
|
case tokenFontFamilyDefault: // \fnil
|
|
PARSERCOVERAGE_CASE();
|
|
AssertSz(tokenFontFamilyRoman - tokenFontFamilyDefault == 1,
|
|
"CRTFRead::HandleToken: invalid token definition");
|
|
|
|
if(pstate->ptf)
|
|
{
|
|
pstate->ptf->bPitchAndFamily
|
|
= (BYTE)((_token - tokenFontFamilyDefault) << 4
|
|
| (pstate->ptf->bPitchAndFamily & 0xF));
|
|
|
|
// Setup SYMBOL_CHARSET charset for \ftech if there isn't any charset info
|
|
if(tokenFontFamilyTechnical == _token && pstate->ptf->iCharRep == DEFAULT_INDEX)
|
|
pstate->ptf->iCharRep = SYMBOL_INDEX;
|
|
}
|
|
break;
|
|
|
|
case tokenPitch: // \fprq
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->ptf)
|
|
pstate->ptf->bPitchAndFamily
|
|
= (BYTE)(iParam | (pstate->ptf->bPitchAndFamily & 0xF0));
|
|
break;
|
|
|
|
case tokenAnsiCodePage: // \ansicpg
|
|
PARSERCOVERAGE_CASE();
|
|
#if !defined(NOFULLDEBUG) && defined(DEBUG)
|
|
if(_fSeenFontTable && _nCodePage == INVALID_CODEPAGE)
|
|
TRACEWARNSZ("CRTFRead::HandleToken(): Found an \ansicpgN tag after "
|
|
"the font table. Should have code to fix-up "
|
|
"converted font names and document text.");
|
|
#endif
|
|
if(!(_dwFlags & SF_USECODEPAGE))
|
|
{
|
|
_nCodePage = iParam;
|
|
pstate->SetCodePage(iParam);
|
|
}
|
|
Assert(!IsUTF8 || pstate->nCodePage == CP_UTF8);
|
|
break;
|
|
|
|
case tokenCodePage: // \cpg
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->SetCodePage(iParam);
|
|
if(pstate->sDest == destFontTable && pstate->ptf)
|
|
{
|
|
pstate->ptf->sCodePage = (SHORT)iParam;
|
|
pstate->ptf->iCharRep = CharRepFromCodePage(iParam);
|
|
|
|
// If a document-level code page has not been specified,
|
|
// grab this from the first font table entry containing a
|
|
// \fcharsetN or \cpgN
|
|
if(_nCodePage == INVALID_CODEPAGE)
|
|
_nCodePage = iParam;
|
|
}
|
|
break;
|
|
|
|
case tokenCharSet: // \fcharset N
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->ptf)
|
|
{
|
|
pstate->ptf->iCharRep = CharRepFromCharSet((BYTE)iParam);
|
|
pstate->ptf->sCodePage = (SHORT)CodePageFromCharRep(pstate->ptf->iCharRep);
|
|
pstate->SetCodePage(pstate->ptf->sCodePage);
|
|
|
|
// If a document-level code page has not been specified,
|
|
// grab this from the first font table entry containing a
|
|
// \fcharsetN or \cpgN
|
|
if (pstate->nCodePage != CP_SYMBOL &&
|
|
_nCodePage == INVALID_CODEPAGE)
|
|
{
|
|
_nCodePage = pstate->nCodePage;
|
|
}
|
|
if(IsRTLCharSet(iParam))
|
|
{
|
|
if(_sDefaultBiDiFont == -1)
|
|
_sDefaultBiDiFont = pstate->ptf->sHandle;
|
|
|
|
if(!IsRTLCharRep(_iCharRepBiDi))
|
|
_iCharRepBiDi = pstate->ptf->iCharRep;
|
|
}
|
|
_fCharSet = TRUE;
|
|
if(iParam)
|
|
_fNon0CharSet = TRUE; // Not HTML converter
|
|
}
|
|
break;
|
|
|
|
case tokenRealFontName: // \fname
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destRealFontName;
|
|
break;
|
|
|
|
case tokenAssocFontSelect: // \af N
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->rgDefFont[pstate->iDefFont].sHandle = iParam;
|
|
iParam = 0; // Fall thru to \afs N to 0 sSize
|
|
|
|
case tokenAssocFontSize: // \afs N
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->rgDefFont[pstate->iDefFont].sSize = iParam;
|
|
break;
|
|
|
|
case tokenFontSelect: // \f N
|
|
PARSERCOVERAGE_CASE();
|
|
if(iParam == -1) // Can't handle this bizarre choice
|
|
goto skip_group;
|
|
|
|
if(pstate->sDest == destFontTable) // Building font table
|
|
{
|
|
if(iParam == _sDefaultFont)
|
|
{
|
|
_fReadDefFont = TRUE;
|
|
ptf = _fonts.Elem(0);
|
|
}
|
|
else if(iParam == _sDefaultBiDiFont)
|
|
ptf = _fonts.Elem(1);
|
|
|
|
else if(!(ptf =_fonts.Add(1,NULL))) // Make room in font table for
|
|
{ // font to be parsed
|
|
OutOfRAM:
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
break;
|
|
}
|
|
pstate->ptf = ptf;
|
|
ptf->sHandle = (SHORT)iParam; // Save handle
|
|
ptf->szName[0] = '\0'; // Start with null string
|
|
ptf->bPitchAndFamily = 0;
|
|
ptf->fNameIsDBCS = FALSE;
|
|
ptf->sCodePage = INVALID_CODEPAGE;
|
|
ptf->fCpgFromSystem = FALSE;
|
|
ptf->iCharRep = DEFAULT_INDEX;
|
|
}
|
|
else if(_fonts.Count() && pstate->sDest != destStyleSheet) // Font switch in text
|
|
{
|
|
SHORT idx = DEFFONT_LTRCH;
|
|
|
|
SelectCurrentFont(iParam);
|
|
if(IsRTLCharRep(pstate->ptf->iCharRep))
|
|
{
|
|
_iCharRepBiDi = pstate->ptf->iCharRep;
|
|
idx = DEFFONT_RTLCH;
|
|
if(pstate->iDefFont == DEFFONT_LTRCH)
|
|
pstate->iDefFont = DEFFONT_RTLCH;
|
|
}
|
|
pstate->rgDefFont[idx].sHandle = iParam;
|
|
pstate->rgDefFont[idx].sSize = 0;
|
|
}
|
|
break;
|
|
|
|
case tokenDBChars: // \dbch
|
|
case tokenHIChars: // \hich
|
|
case tokenLOChars: // \loch
|
|
case tokenRToLChars: // \rtlch
|
|
case tokenLToRChars: // \ltrch
|
|
pstate->iDefFont = _token - tokenLToRChars + DEFFONT_LTRCH;
|
|
if(!IN_RANGE(DEFFONT_LTRCH, pstate->iDefFont, DEFFONT_RTLCH))
|
|
break;
|
|
i = pstate->rgDefFont[pstate->iDefFont].sHandle;
|
|
if(i == -1)
|
|
break;
|
|
SelectCurrentFont(i);
|
|
HandleNumber(); // Fix Word \ltrchN bug
|
|
iParam = pstate->rgDefFont[pstate->iDefFont].sSize;
|
|
if(!iParam)
|
|
break; // No \afs N value specified
|
|
// Fall thru to \fs N
|
|
case tokenFontSize: // \fs N
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->rgDefFont[pstate->iDefFont].sSize = iParam;
|
|
_CF._yHeight = PointsToFontHeight(iParam); // Convert font size in
|
|
_dwMaskCF |= CFM_SIZE; // half points to logical
|
|
break; // units
|
|
|
|
// NOTE: \*\fontemb and \*\fontfile are discarded. The font mapper will
|
|
// have to do the best it can given font name, family, and pitch.
|
|
// Embedded fonts are particularly nasty because legal use should
|
|
// only support read-only which parser cannot enforce.
|
|
|
|
case tokenLanguage: // \lang N
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._lcid = MAKELCID(iParam, SORT_DEFAULT);
|
|
_dwMaskCF |= CFM_LCID;
|
|
|
|
if(W32->IsBiDiLcid(_CF._lcid))
|
|
{
|
|
_iCharRepBiDi = CharRepFromLID(iParam);
|
|
if(pstate->iDefFont == DEFFONT_LTRCH) // Workaround Word 10 bug
|
|
pstate->iDefFont = DEFFONT_RTLCH;
|
|
}
|
|
break;
|
|
|
|
|
|
//-------------------------- Color Control Words ------------------------------
|
|
|
|
case tokenColorTable: // \colortbl
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destColorTable;
|
|
_fGetColorYet = FALSE;
|
|
break;
|
|
|
|
case tokenColorRed: // \red
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->bRed = (BYTE)iParam;
|
|
_fGetColorYet = TRUE;
|
|
break;
|
|
|
|
case tokenColorGreen: // \green
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->bGreen = (BYTE)iParam;
|
|
_fGetColorYet = TRUE;
|
|
break;
|
|
|
|
case tokenColorBlue: // \blue
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->bBlue = (BYTE)iParam;
|
|
_fGetColorYet = TRUE;
|
|
break;
|
|
|
|
case tokenColorForeground: // \cf
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._crTextColor = GetColor(CFM_COLOR);
|
|
break;
|
|
|
|
case tokenColorBackground: // \highlight
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._crBackColor = GetColor(CFM_BACKCOLOR);
|
|
break;
|
|
|
|
case tokenExpand: // \expndtw N
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._sSpacing = (SHORT) iParam;
|
|
_dwMaskCF |= CFM_SPACING;
|
|
break;
|
|
|
|
case tokenCharStyle: // \cs N
|
|
PARSERCOVERAGE_CASE();
|
|
/* FUTURE (alexgo): we may want to support character styles
|
|
in some future version.
|
|
_CF._sStyle = (SHORT)iParam;
|
|
_dwMaskCF |= CFM_STYLE; */
|
|
|
|
if(pstate->sDest == destStyleSheet)
|
|
goto skip_group;
|
|
break;
|
|
|
|
case tokenAnimText: // \animtext N
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._bAnimation = (BYTE)iParam;
|
|
_dwMaskCF |= CFM_ANIMATION;
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
break;
|
|
|
|
case tokenKerning: // \kerning N
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._wKerning = (WORD)(10 * iParam); // Convert to twips
|
|
_dwMaskCF |= CFM_KERNING;
|
|
break;
|
|
|
|
case tokenHorzInVert: // \horzvert N
|
|
PARSERCOVERAGE_CASE();
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
break;
|
|
|
|
case tokenFollowingPunct: // \*\fchars
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destFollowingPunct;
|
|
{
|
|
char *pwchBuf=NULL;
|
|
if (ReadRawText((_dwFlags & SFF_SELECTION) ? NULL : &pwchBuf) && pwchBuf)
|
|
{
|
|
if (_ped->SetFollowingPunct(pwchBuf) != NOERROR) // Store this buffer inside doc
|
|
FreePv(pwchBuf);
|
|
}
|
|
else if (pwchBuf)
|
|
FreePv(pwchBuf);
|
|
}
|
|
break;
|
|
|
|
case tokenLeadingPunct: // \*\lchars
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destLeadingPunct;
|
|
{
|
|
char *pwchBuf=NULL;
|
|
if (ReadRawText((_dwFlags & SFF_SELECTION) ? NULL : &pwchBuf) && pwchBuf)
|
|
{
|
|
if (_ped->SetLeadingPunct(pwchBuf) != NOERROR) // Store this buffer inside doc
|
|
FreePv(pwchBuf);
|
|
}
|
|
else if (pwchBuf)
|
|
FreePv(pwchBuf);
|
|
}
|
|
break;
|
|
|
|
case tokenDocumentArea: // \info
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destDocumentArea;
|
|
break;
|
|
|
|
case tokenVerticalRender: // \vertdoc
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("Vertical" );
|
|
if (!(_dwFlags & SFF_SELECTION))
|
|
HandleSTextFlow(1);
|
|
break;
|
|
|
|
case tokenSTextFlow: // \stextflow N
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("STextFlow" );
|
|
if (!(_dwFlags & SFF_SELECTION) && !_ped->Get10Mode())
|
|
HandleSTextFlow(iParam);
|
|
break;
|
|
|
|
#ifdef FE
|
|
USHORT usPunct; // Used for FE word breaking
|
|
|
|
case tokenNoOverflow: // \nooverflow
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("No Overflow");
|
|
usPunct = ~WBF_OVERFLOW;
|
|
goto setBrkOp;
|
|
|
|
case tokenNoWordBreak: // \nocwrap
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("No Word Break" );
|
|
usPunct = ~WBF_WORDBREAK;
|
|
goto setBrkOp;
|
|
|
|
case tokenNoWordWrap: // \nowwrap
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("No Word Word Wrap" );
|
|
usPunct = ~WBF_WORDWRAP;
|
|
|
|
setBrkOp:
|
|
if(!(_dwFlags & fRTFFE))
|
|
{
|
|
usPunct &= UsVGetBreakOption(_ped->lpPunctObj);
|
|
UsVSetBreakOption(_ped->lpPunctObj, usPunct);
|
|
}
|
|
break;
|
|
|
|
case tokenHorizontalRender: // \horzdoc
|
|
PARSERCOVERAGE_CASE();
|
|
TRACEINFOSZ("Horizontal" );
|
|
if(pstate->sDest == destDocumentArea && !(_dwFlags & fRTFFE))
|
|
_ped->fModeDefer = FALSE;
|
|
break;
|
|
|
|
#endif
|
|
//-------------------- Character Format Control Words -----------------------------
|
|
|
|
case tokenUnderlineThickLongDash: // \ulthldash [18]
|
|
case tokenUnderlineThickDotted: // \ulthd [17]
|
|
case tokenUnderlineThickDashDotDot: // \ulthdashdd [16]
|
|
case tokenUnderlineThickDashDot: // \ulthdashd [15]
|
|
case tokenUnderlineThickDash: // \ulthdash [14]
|
|
case tokenUnderlineLongDash: // \ulldash [13]
|
|
case tokenUnderlineHeavyWave: // \ulhwave [12]
|
|
case tokenUnderlineDoubleWave: // \ululdbwave [11]
|
|
case tokenUnderlineHairline: // \ulhair [10]
|
|
case tokenUnderlineThick: // \ulth [9]
|
|
case tokenUnderlineDouble: // \uldb [3]
|
|
case tokenUnderlineWord: // \ulw [2]
|
|
// CheckNotifyLowFiRTF();
|
|
|
|
case tokenUnderlineWave: // \ulwave [8]
|
|
case tokenUnderlineDashDotDotted: // \uldashdd [7]
|
|
case tokenUnderlineDashDotted: // \uldashd [6]
|
|
case tokenUnderlineDash: // \uldash [5]
|
|
case tokenUnderlineDotted: // \uld [4]
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._bUnderlineType = (BYTE)(_token - tokenUnderlineWord + 2);
|
|
_token = tokenUnderline; // CRenderer::RenderUnderline()
|
|
goto under; // reveals which of these are
|
|
// rendered specially
|
|
case tokenUnderline: // \ul [Effect 4]
|
|
PARSERCOVERAGE_CASE(); // (see handleCF)
|
|
_CF._bUnderlineType = CFU_UNDERLINE;
|
|
under: _dwMaskCF |= CFM_UNDERLINETYPE;
|
|
goto handleCF;
|
|
|
|
case tokenDeleted: // \deleted
|
|
PARSERCOVERAGE_CASE();
|
|
_dwMaskCF2 = CFM2_DELETED;
|
|
dwT = CFE_DELETED;
|
|
goto hndlCF;
|
|
|
|
// These effects are turned on if their control word parameter is missing
|
|
// or nonzero. They are turned off if the parameter is zero. This
|
|
// behavior is usually identified by an asterisk (*) in the RTF spec.
|
|
// The code uses fact that CFE_xxx = CFM_xxx
|
|
case tokenImprint: // \impr [1000]
|
|
case tokenEmboss: // \embo [800]
|
|
case tokenShadow: // \shad [400]
|
|
case tokenOutline: // \outl [200]
|
|
case tokenSmallCaps: // \scaps [40]
|
|
CheckNotifyLowFiRTF();
|
|
|
|
handleCF:
|
|
case tokenRevised: // \revised [4000]
|
|
case tokenDisabled: // \disabled [2000]
|
|
case tokenHiddenText: // \v [100]
|
|
case tokenCaps: // \caps [80]
|
|
case tokenLink: // \link [20]
|
|
case tokenProtect: // \protect [10]
|
|
case tokenStrikeOut: // \strike [8]
|
|
case tokenItalic: // \i [2]
|
|
case tokenBold: // \b [1]
|
|
PARSERCOVERAGE_CASE();
|
|
dwT = 1 << (_token - tokenBold); // Generate effect mask
|
|
_dwMaskCF |= dwT;
|
|
hndlCF: _CF._dwEffects &= ~dwT; // Default attribute off
|
|
if(!_fParam || _iParam) // Effect is on
|
|
_CF._dwEffects |= dwT; // In either case, the effect
|
|
break; // is defined
|
|
|
|
case tokenStopUnderline: // \ulnone
|
|
PARSERCOVERAGE_CASE();
|
|
_CF._dwEffects &= ~CFE_UNDERLINE; // Kill all underlining
|
|
_dwMaskCF |= CFM_UNDERLINE;
|
|
break;
|
|
|
|
case tokenRevAuthor: // \revauth N
|
|
PARSERCOVERAGE_CASE();
|
|
/* FUTURE: (alexgo) this doesn't work well now since we don't support
|
|
revision tables. We may want to support this better in the future.
|
|
So what we do now is the 1.0 technique of using a color for the
|
|
author */
|
|
if(iParam > 0)
|
|
{
|
|
_CF._dwEffects &= ~CFE_AUTOCOLOR;
|
|
_dwMaskCF |= CFM_COLOR;
|
|
_CF._crTextColor = rgcrRevisions[(iParam - 1) & REVMASK];
|
|
}
|
|
break;
|
|
|
|
case tokenUp: // \up
|
|
PARSERCOVERAGE_CASE();
|
|
dy = 10;
|
|
goto StoreOffset;
|
|
|
|
case tokenDown: // \down
|
|
PARSERCOVERAGE_CASE();
|
|
dy = -10;
|
|
|
|
StoreOffset:
|
|
if(!_fParam)
|
|
iParam = dyDefaultSuperscript;
|
|
_CF._yOffset = iParam * dy; // Half points->twips
|
|
_dwMaskCF |= CFM_OFFSET;
|
|
break;
|
|
|
|
case tokenSuperscript: // \super
|
|
PARSERCOVERAGE_CASE();
|
|
dwT = CFE_SUPERSCRIPT;
|
|
goto SetSubSuperScript;
|
|
|
|
case tokenSubscript: // \sub
|
|
PARSERCOVERAGE_CASE();
|
|
dwT = CFE_SUBSCRIPT;
|
|
goto SetSubSuperScript;
|
|
|
|
case tokenNoSuperSub: // \nosupersub
|
|
PARSERCOVERAGE_CASE();
|
|
dwT = 0;
|
|
SetSubSuperScript:
|
|
_dwMaskCF |= (CFE_SUPERSCRIPT | CFE_SUBSCRIPT);
|
|
_CF._dwEffects &= ~(CFE_SUPERSCRIPT | CFE_SUBSCRIPT);
|
|
_CF._dwEffects |= dwT;
|
|
break;
|
|
|
|
|
|
|
|
//--------------------- Paragraph Control Words -----------------------------
|
|
|
|
case tokenStyleSheet: // \stylesheet
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destStyleSheet;
|
|
_Style = 0; // Default normal style
|
|
break;
|
|
|
|
case tokenTabBar: // \tb
|
|
PARSERCOVERAGE_CASE();
|
|
_bTabType = PFT_BAR; // Fall thru to \tx
|
|
|
|
case tokenTabPosition: // \tx
|
|
PARSERCOVERAGE_CASE();
|
|
if(_cTab < MAX_TAB_STOPS && (unsigned)iParam < 0x1000000)
|
|
{
|
|
_rgxCell[_cTab++] = GetTabPos(iParam)
|
|
+ (_bTabType << 24) + (_bTabLeader << 28);
|
|
_dwMaskPF |= PFM_TABSTOPS;
|
|
}
|
|
_cCell = 0; // Invalidate _rgxCell array
|
|
break; // for table purposes
|
|
|
|
case tokenDecimalTab: // \tqdec
|
|
case tokenFlushRightTab: // \tqr
|
|
case tokenCenterTab: // \tqc
|
|
PARSERCOVERAGE_CASE();
|
|
_bTabType = (BYTE)(_token - tokenCenterTab + PFT_CENTER);
|
|
break;
|
|
|
|
case tokenTabLeaderEqual: // \tleq
|
|
case tokenTabLeaderThick: // \tlth
|
|
case tokenTabLeaderUnderline: // \tlul
|
|
case tokenTabLeaderHyphen: // \tlhyph
|
|
CheckNotifyLowFiRTF();
|
|
case tokenTabLeaderDots: // \tldot
|
|
PARSERCOVERAGE_CASE();
|
|
_bTabLeader = (BYTE)(_token - tokenTabLeaderDots + PFTL_DOTS);
|
|
break;
|
|
|
|
// The following need to be kept in sync with PFE_xxx
|
|
case tokenRToLPara: // \rtlpar
|
|
_ped->OrCharFlags(FRTL);
|
|
|
|
case tokenCollapsed: // \collapsed
|
|
case tokenSideBySide: // \sbys
|
|
case tokenHyphPar: // \hyphpar
|
|
case tokenNoWidCtlPar: // \nowidctlpar
|
|
case tokenNoLineNumber: // \noline
|
|
case tokenPageBreakBefore: // \pagebb
|
|
case tokenKeepNext: // \keepn
|
|
case tokenKeep: // \keep
|
|
PARSERCOVERAGE_CASE();
|
|
wT = (WORD)(1 << (_token - tokenRToLPara));
|
|
_PF._wEffects |= wT;
|
|
_dwMaskPF |= (wT << 16);
|
|
break;
|
|
|
|
case tokenLToRPara: // \ltrpar
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._wEffects &= ~PFE_RTLPARA;
|
|
_dwMaskPF |= PFM_RTLPARA;
|
|
break;
|
|
|
|
case tokenLineSpacing: // \sl N
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._dyLineSpacing = abs(iParam);
|
|
_PF._bLineSpacingRule // Handle nonmultiple rules
|
|
= (BYTE)(!iParam || iParam == 1000
|
|
? 0 : (iParam > 0) ? tomLineSpaceAtLeast
|
|
: tomLineSpaceExactly); // \slmult can change (has to
|
|
_dwMaskPF |= PFM_LINESPACING; // follow if it appears)
|
|
break;
|
|
|
|
case tokenDropCapLines: // \dropcapliN
|
|
if(_PF._bLineSpacingRule == tomLineSpaceExactly) // Don't chop off
|
|
_PF._bLineSpacingRule = tomLineSpaceAtLeast; // drop cap
|
|
_fBody = TRUE;
|
|
break;
|
|
|
|
case tokenLineSpacingRule: // \slmult N
|
|
PARSERCOVERAGE_CASE();
|
|
if(iParam)
|
|
{ // It's multiple line spacing
|
|
_PF._bLineSpacingRule = tomLineSpaceMultiple;
|
|
_PF._dyLineSpacing /= 12; // RE line spacing multiple is
|
|
_dwMaskPF |= PFM_LINESPACING; // given in 20ths of a line,
|
|
} // while RTF uses 240ths
|
|
break;
|
|
|
|
case tokenSpaceBefore: // \sb N
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._dySpaceBefore = iParam;
|
|
_dwMaskPF |= PFM_SPACEBEFORE;
|
|
break;
|
|
|
|
case tokenSpaceAfter: // \sa N
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._dySpaceAfter = iParam;
|
|
_dwMaskPF |= PFM_SPACEAFTER;
|
|
break;
|
|
|
|
case tokenStyle: // \s N
|
|
PARSERCOVERAGE_CASE();
|
|
_Style = iParam; // Save it in case in StyleSheet
|
|
if(pstate->sDest != destStyleSheet)
|
|
{ // Select possible heading level
|
|
_PF._sStyle = STYLE_NORMAL; // Default Normal style
|
|
_PF._bOutlineLevel |= 1;
|
|
|
|
for(i = 0; i < NSTYLES && iParam != _rgStyles[i]; i++)
|
|
; // Check for heading style
|
|
if(i < NSTYLES) // Found one
|
|
{
|
|
_PF._sStyle = (SHORT)(-i - 1); // Store desired heading level
|
|
_PF._bOutlineLevel = (BYTE)(2*(i-1));// Update outline level for
|
|
} // nonheading styles
|
|
_dwMaskPF |= PFM_ALLRTF;
|
|
}
|
|
break;
|
|
|
|
case tokenIndentFirst: // \fi N
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._dxStartIndent += _PF._dxOffset // Cancel current offset
|
|
+ iParam; // and add in new one
|
|
_PF._dxOffset = -iParam; // Offset for all but 1st line
|
|
// = -RTF_FirstLineIndent
|
|
_dwMaskPF |= (PFM_STARTINDENT | PFM_OFFSET);
|
|
break;
|
|
|
|
case tokenIndentLeft: // \li N
|
|
case tokenIndentRight: // \ri N
|
|
PARSERCOVERAGE_CASE();
|
|
// AymanA: For RtL para indents has to be flipped.
|
|
Assert(PFE_RTLPARA == 0x0001);
|
|
if((_token == tokenIndentLeft) ^ (_PF.IsRtlPara()))
|
|
{
|
|
_PF._dxStartIndent = iParam - _PF._dxOffset;
|
|
_dwMaskPF |= PFM_STARTINDENT;
|
|
}
|
|
else
|
|
{
|
|
_PF._dxRightIndent = iParam;
|
|
_dwMaskPF |= PFM_RIGHTINDENT;
|
|
}
|
|
break;
|
|
|
|
case tokenAlignLeft: // \ql
|
|
case tokenAlignRight: // \qr
|
|
case tokenAlignCenter: // \qc
|
|
case tokenAlignJustify: // \qj
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._bAlignment = (WORD)(_token - tokenAlignLeft + PFA_LEFT);
|
|
_dwMaskPF |= PFM_ALIGNMENT;
|
|
break;
|
|
|
|
case tokenBorderOutside: // \brdrbar
|
|
case tokenBorderBetween: // \brdrbtw
|
|
case tokenBorderShadow: // \brdrsh
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._dwBorderColor |= 1 << (_token - tokenBorderShadow + 20);
|
|
_dwBorderColors = _PF._dwBorderColor;
|
|
break;
|
|
|
|
// Paragraph and cell border segments
|
|
case tokenBox: // \box
|
|
PARSERCOVERAGE_CASE();
|
|
CheckNotifyLowFiRTF();
|
|
_PF._wEffects |= PFE_BOX;
|
|
_dwMaskPF |= PFM_BOX;
|
|
_bBorder = 0; // Store parms as if for
|
|
break; // \brdrt
|
|
|
|
case tokenBorderBottom: // \brdrb
|
|
case tokenBorderRight: // \brdrr
|
|
case tokenBorderTop: // \brdrt
|
|
if((rgKeyword[_iKeyword].szKeyword[0] | 0x20) != 't')
|
|
CheckNotifyLowFiRTF();
|
|
case tokenBorderLeft: // \brdrl
|
|
|
|
case tokenCellBorderBottom: // \clbrdrb
|
|
case tokenCellBorderRight: // \clbrdrr
|
|
case tokenCellBorderTop: // \clbrdrt
|
|
case tokenCellBorderLeft: // \clbrdrl
|
|
PARSERCOVERAGE_CASE();
|
|
_bBorder = (BYTE)(_token - tokenBorderLeft);
|
|
break;
|
|
|
|
// Paragraph border styles
|
|
case tokenBorderTriple: // \brdrtriple
|
|
case tokenBorderDoubleThick: // \brdrth
|
|
case tokenBorderSingleThick: // \brdrs
|
|
case tokenBorderHairline: // \brdrhair
|
|
case tokenBorderDot: // \brdrdot
|
|
case tokenBorderDouble: // \brdrdb
|
|
case tokenBorderDashSmall: // \brdrdashsm
|
|
case tokenBorderDash: // \brdrdash
|
|
PARSERCOVERAGE_CASE();
|
|
if(_bBorder < 4) // Only for paragraphs
|
|
SetBorderParm(_PF._wBorders, _token - tokenBorderDash);
|
|
break;
|
|
|
|
case tokenBorderColor: // \brdrcf
|
|
PARSERCOVERAGE_CASE();
|
|
if(_bBorder < 4) // Only for paragraphs
|
|
{
|
|
iParam = GetStandardColorIndex();
|
|
_PF._dwBorderColor &= ~(0x1F << (5*_bBorder));
|
|
_PF._dwBorderColor |= iParam << (5*_bBorder);
|
|
_dwBorderColors = _PF._dwBorderColor;
|
|
}
|
|
else // Cell borders
|
|
_dwCellColors |= GetCellColorIndex() << (5*(_bBorder - 4));
|
|
break;
|
|
|
|
case tokenBorderWidth: // \brdrw
|
|
PARSERCOVERAGE_CASE(); // Store width in half pts
|
|
// iParam is in twips
|
|
if(_bBorder < 4) // Paragraphs
|
|
{
|
|
iParam = TwipsToQuarterPoints(iParam);
|
|
SetBorderParm(_PF._wBorderWidth, iParam);
|
|
}
|
|
else // Table cells
|
|
{
|
|
iParam = CheckTwips(iParam);
|
|
_dwCellBrdrWdths |= iParam << 8*(_bBorder - 4);
|
|
}
|
|
break;
|
|
|
|
case tokenBorderSpace: // \brsp
|
|
PARSERCOVERAGE_CASE(); // Space is in pts
|
|
if(_bBorder < 4) // Only for paragraphs
|
|
SetBorderParm(_PF._wBorderSpace, iParam/20);// iParam is in twips
|
|
break;
|
|
|
|
// Paragraph shading
|
|
case tokenBckgrndVert: // \bgvert
|
|
case tokenBckgrndHoriz: // \bghoriz
|
|
case tokenBckgrndFwdDiag: // \bgfdiag
|
|
case tokenBckgrndDrkVert: // \bgdkvert
|
|
case tokenBckgrndDrkHoriz: // \bgdkhoriz
|
|
case tokenBckgrndDrkFwdDiag: // \bgdkfdiag
|
|
case tokenBckgrndDrkDiagCross: // \bgdkdcross
|
|
case tokenBckgrndDrkCross: // \bgdkcross
|
|
case tokenBckgrndDrkBckDiag: // \bgdkbdiag
|
|
case tokenBckgrndDiagCross: // \bgdcross
|
|
case tokenBckgrndCross: // \bgcross
|
|
case tokenBckgrndBckDiag: // \bgbdiag
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._wShadingStyle = (WORD)((_PF._wShadingStyle & 0xFFC0)
|
|
| (_token - tokenBckgrndBckDiag + 1));
|
|
_dwMaskPF |= PFM_SHADING;
|
|
break;
|
|
|
|
case tokenColorBckgrndPat: // \cbpat
|
|
PARSERCOVERAGE_CASE();
|
|
iParam = GetStandardColorIndex();
|
|
_PF._wShadingStyle = (WORD)((_PF._wShadingStyle & 0x07FF) | (iParam << 11));
|
|
_dwMaskPF |= PFM_SHADING;
|
|
break;
|
|
|
|
case tokenColorForgrndPat: // \cfpat
|
|
PARSERCOVERAGE_CASE();
|
|
iParam = GetStandardColorIndex();
|
|
_PF._wShadingStyle = (WORD)((_PF._wShadingStyle & 0xF83F) | (iParam << 6));
|
|
_dwMaskPF |= PFM_SHADING;
|
|
break;
|
|
|
|
case tokenShading: // \shading
|
|
PARSERCOVERAGE_CASE();
|
|
_PF._wShadingWeight = (WORD)iParam;
|
|
_dwMaskPF |= PFM_SHADING;
|
|
break;
|
|
|
|
// Paragraph numbering
|
|
case tokenParaNum: // \pn
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destParaNumbering;
|
|
pstate->fBullet = FALSE;
|
|
_PF._wNumberingStart = 1;
|
|
_dwMaskPF |= PFM_NUMBERINGSTART;
|
|
break;
|
|
|
|
case tokenParaNumIndent: // \pnindent N
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->sDest == destParaNumbering)
|
|
pstate->sIndentNumbering = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenParaNumStart: // \pnstart N
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->sDest == destParaNumbering)
|
|
{
|
|
_PF._wNumberingStart = (WORD)iParam;
|
|
_dwMaskPF |= PFM_NUMBERINGSTART;
|
|
}
|
|
break;
|
|
|
|
case tokenParaNumCont: // \pnlvlcont
|
|
PARSERCOVERAGE_CASE();
|
|
_prg->_rpPF.AdjustBackward(); // Maintain numbering mode
|
|
_PF._wNumbering = _prg->GetPF()->_wNumbering;
|
|
_prg->_rpPF.AdjustForward();
|
|
_wNumberingStyle = PFNS_NONUMBER; // Signal no number
|
|
_dwMaskPF |= PFM_NUMBERING; // Note: can be new para with
|
|
pstate->fBullet = TRUE; // its own indents
|
|
break;
|
|
|
|
case tokenParaNumBody: // \pnlvlbody
|
|
PARSERCOVERAGE_CASE();
|
|
_wNumberingStyle = PFNS_PAREN;
|
|
_token = tokenParaNumDecimal; // Default to decimal
|
|
goto setnum;
|
|
|
|
case tokenParaNumBullet: // \pnlvlblt
|
|
_wNumberingStyle = 0; // Reset numbering styles
|
|
goto setnum;
|
|
|
|
case tokenParaNumDecimal: // \pndec
|
|
case tokenParaNumLCLetter: // \pnlcltr
|
|
case tokenParaNumUCLetter: // \pnucltr
|
|
case tokenParaNumLCRoman: // \pnlcrm
|
|
case tokenParaNumUCRoman: // \pnucrm
|
|
PARSERCOVERAGE_CASE();
|
|
if(_PF._wNumbering == PFN_BULLET && pstate->fBullet)
|
|
break; // Ignore above for bullets
|
|
|
|
setnum: if(pstate->sDest == destParaNumbering)
|
|
{
|
|
_PF._wNumbering = (WORD)(PFN_BULLET + _token - tokenParaNumBullet);
|
|
_dwMaskPF |= PFM_NUMBERING;
|
|
pstate->fBullet = TRUE; // We do bullets, so don't
|
|
} // output the \pntext group
|
|
break;
|
|
|
|
case tokenParaNumText: // \pntext
|
|
PARSERCOVERAGE_CASE();
|
|
// Throw away previously read paragraph numbering and use
|
|
// the most recently read to apply to next run of text.
|
|
_cchUsedNumText = 0;
|
|
pstate->sDest = destParaNumText;
|
|
break;
|
|
|
|
case tokenParaNumAlignCenter: // \pnqc
|
|
case tokenParaNumAlignRight: // \pnqr
|
|
PARSERCOVERAGE_CASE();
|
|
_wNumberingStyle = (_wNumberingStyle & ~3) | _token - tokenParaNumAlignCenter + 1;
|
|
break;
|
|
|
|
case tokenPictureQuickDraw: // \macpict
|
|
case tokenPictureOS2Metafile: // \pmmetafile
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
|
|
case tokenParaNumAfter: // \pntxta
|
|
case tokenParaNumBefore: // \pntxtb
|
|
PARSERCOVERAGE_CASE();
|
|
|
|
skip_group:
|
|
if(!SkipToEndOfGroup())
|
|
{
|
|
// During \fonttbl processing, we may hit unknown destinations,
|
|
// e.g., \panose, that cause the HandleEndGroup to select the
|
|
// default font, which may not be defined yet. So, we change
|
|
// sDest to avoid this problem.
|
|
if(pstate->sDest == destFontTable || pstate->sDest == destStyleSheet)
|
|
pstate->sDest = destNULL;
|
|
HandleEndGroup();
|
|
}
|
|
break;
|
|
|
|
// Tables
|
|
case tokenInTable: // \intbl
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->sDest != destRTF && pstate->sDest != destFieldResult &&
|
|
pstate->sDest != destParaNumText)
|
|
{
|
|
_ecParseError = ecUnexpectedToken;
|
|
break;
|
|
}
|
|
if(!_iCell && !_bTableLevel)
|
|
DelimitRow(szRowStart); // Start row
|
|
break;
|
|
|
|
case tokenNestCell: // \nestcell
|
|
case tokenCell: // \cell
|
|
PARSERCOVERAGE_CASE();
|
|
HandleCell();
|
|
break;
|
|
|
|
case tokenRowHeight: // \trrh N
|
|
PARSERCOVERAGE_CASE();
|
|
_dyRow = iParam;
|
|
break;
|
|
|
|
case tokenCellHalfGap: // \trgaph N
|
|
PARSERCOVERAGE_CASE(); // Save half space between
|
|
if((unsigned)iParam > 255) // Illegal value: use default
|
|
iParam = 108;
|
|
_dxCell = iParam; // cells to add to tabs
|
|
break; // Roundtrip value at end of
|
|
// tab array
|
|
case tokenCellX: // \cellx N
|
|
PARSERCOVERAGE_CASE();
|
|
HandleCellx(iParam);
|
|
break;
|
|
|
|
case tokenRowDefault: // \trowd
|
|
PARSERCOVERAGE_CASE();
|
|
if(_ped->fUsePassword() || pstate->sDest == destParaNumText)
|
|
{
|
|
_ecParseError = ecUnexpectedToken;
|
|
break;
|
|
}
|
|
// Insert a newline if we are inserting a table behind characters in the
|
|
// same line. This follows the Word9 model.
|
|
if (_cpFirst == _prg->GetCp() && _cpThisPara != _cpFirst)
|
|
{
|
|
EC ec = _ped->fUseCRLF() // If RichEdit 1.0 compatibility
|
|
? HandleText(szaCRLF, ALL_ASCII)// mode, use CRLF; else CR
|
|
: HandleChar((unsigned)(CR));
|
|
if(ec == ecNoError)
|
|
_cpThisPara = _prg->GetCp(); // New para starts after CRLF
|
|
}
|
|
|
|
_cCell = 0; // No cell right boundaries
|
|
_dxCell = 0; // or half gap defined yet
|
|
_xRowOffset = 0;
|
|
_dwCellBrdrWdths = 0;
|
|
_dyRow = 0; // No row height yet
|
|
_wBorderWidth = 0; // No borders yet
|
|
_dwBorderColors = 0; // No colors yet
|
|
_dwCellColors = 0; // No colors yet
|
|
_dwShading = 0; // No shading yet
|
|
_bAlignment = PFA_LEFT;
|
|
_iTabsTable = -1; // No cell widths yet
|
|
_bCellFlags = 0; // No cell vert merge
|
|
_crCellCustom1 = 0;
|
|
_crCellCustom2 = 0;
|
|
_fRTLRow = FALSE;
|
|
break;
|
|
|
|
case tokenRowLeft: // \trleft N
|
|
PARSERCOVERAGE_CASE();
|
|
_xRowOffset = iParam;
|
|
break;
|
|
|
|
case tokenRowAlignCenter: // \trqc
|
|
case tokenRowAlignRight: // \trqr
|
|
PARSERCOVERAGE_CASE();
|
|
_bAlignment = (WORD)(_token - tokenRowAlignRight + PFA_RIGHT);
|
|
break;
|
|
|
|
case tokenRToLRow: // \rtlrow
|
|
_fRTLRow = TRUE;
|
|
break;
|
|
|
|
case tokenNestRow: // \nestrow
|
|
_fNo_iTabsTable = TRUE;
|
|
goto row;
|
|
|
|
case tokenRow: // \row
|
|
PARSERCOVERAGE_CASE();
|
|
_iTabsLevel1 = -1;
|
|
row:
|
|
if(!_bTableLevel) // Ignore \row and \nestrow if not in table
|
|
break;
|
|
while(_iCell < _cCell) // If not enuf cells, add
|
|
HandleCell(); // them since Word crashes
|
|
DelimitRow(szRowEnd);
|
|
if(_fNo_iTabsTable && !_bTableLevel) // New nested table format
|
|
InitializeTableRowParms(); // used so reset _cCell
|
|
break; // (new values will be given)
|
|
|
|
case tokenCellBackColor: // \clcbpat N
|
|
_dwCellColors |= GetCellColorIndex() << 4*5;
|
|
break;
|
|
|
|
case tokenCellForeColor: // \clcfpat N
|
|
_dwCellColors |= GetCellColorIndex() << 5*5;
|
|
break;
|
|
|
|
case tokenCellShading: // \clshdng N
|
|
_dwShading = iParam/50; // Store in .5 per cents
|
|
break; // (N is in .01 per cent)
|
|
|
|
case tokenCellAlignBottom: // \clvertalb
|
|
case tokenCellAlignCenter: // \clvertalc
|
|
PARSERCOVERAGE_CASE();
|
|
_bCellFlags |= _token - tokenCellAlignCenter + 1;
|
|
break;
|
|
|
|
case tokenCellMergeDown: // \clvmgf
|
|
_bCellFlags |= fTopCell >> 24;
|
|
break;
|
|
|
|
case tokenCellMergeUp: // \clvmrg
|
|
_bCellFlags |= fLowCell >> 24;
|
|
break;
|
|
|
|
case tokenCellTopBotRLVert: // \cltxtbrlv
|
|
PARSERCOVERAGE_CASE();
|
|
_bCellFlags |= fVerticalCell >> 24;
|
|
break;
|
|
|
|
case tokenCellLRTB: // \cltxlrtb
|
|
break; // This is the default
|
|
// so don't fire LowFiRTF
|
|
case tokenTableLevel: // \itap N
|
|
PARSERCOVERAGE_CASE(); // Set table level
|
|
if(pstate->fShape) // Bogus shape RTF
|
|
break;
|
|
AssertSz(iParam >= _bTableLevel,
|
|
"CRTFRead::HandleToken: illegal itap N");
|
|
if(iParam)
|
|
{
|
|
if(pstate->sDest != destRTF && pstate->sDest != destFieldResult || iParam > 127)
|
|
goto abort;
|
|
_iTabsTable = -1; // Previous cell widths invalid
|
|
_cCell = 0;
|
|
while(iParam > _bTableLevel)
|
|
DelimitRow(szRowStart); // Insert enuf table row headers
|
|
}
|
|
_fNo_iTabsTable = TRUE;
|
|
break;
|
|
|
|
case tokenNestTableProps: // \nesttableprops
|
|
break; // Control word is recognized
|
|
|
|
case tokenNoNestTables: // \nonesttables
|
|
goto skip_group; // Ignore info for nesttable
|
|
// unaware readers
|
|
case tokenPage: // \page
|
|
// FUTURE: we want to be smarter about handling FF. But for
|
|
// now we ignore it for bulletted and number paragraphs
|
|
// and RE 1.0 mode.
|
|
if (_PF._wNumbering != 0 || _ped->Get10Mode())
|
|
break;
|
|
|
|
// Intentional fall thru to EOP
|
|
case tokenEndParagraph: // \par
|
|
case tokenLineBreak: // \line
|
|
PARSERCOVERAGE_CASE();
|
|
HandleEndOfPara();
|
|
break;
|
|
|
|
case tokenParagraphDefault: // \pard
|
|
PARSERCOVERAGE_CASE();
|
|
if(pstate->sDest != destParaNumText) // Ignore if \pn destination
|
|
Pard(pstate);
|
|
break;
|
|
|
|
case tokenEndSection: // \sect
|
|
CheckNotifyLowFiRTF(); // Fall thru to \sectd
|
|
|
|
case tokenSectionDefault: // \sectd
|
|
PARSERCOVERAGE_CASE();
|
|
Pard(pstate);
|
|
break;
|
|
|
|
case tokenBackground: // \background
|
|
if(_dwFlags & SFF_SELECTION) // If pasting a selection,
|
|
goto skip_group; // skip background
|
|
pstate->fBackground = TRUE; // Enable background. NB:
|
|
break; // InitBackground() already called
|
|
|
|
|
|
//----------------------- Field and Group Control Words --------------------------------
|
|
case tokenField: // \field
|
|
PARSERCOVERAGE_CASE();
|
|
|
|
if (pstate->sDest == destDocumentArea ||
|
|
pstate->sDest == destLeadingPunct ||
|
|
pstate->sDest == destFollowingPunct)
|
|
{
|
|
// We're not equipped to handle symbols in these destinations, and
|
|
// we don't want the fields added accidentally to document text.
|
|
goto skip_group;
|
|
}
|
|
pstate->sDest = destField;
|
|
break;
|
|
|
|
case tokenFieldResult: // \fldrslt
|
|
PARSERCOVERAGE_CASE();
|
|
|
|
if(_fSymbolField)
|
|
goto skip_group;
|
|
|
|
pstate->sDest = destFieldResult;
|
|
AddText(pchSeparateField, 2, FALSE);
|
|
break;
|
|
|
|
case tokenFieldInstruction: // \fldinst
|
|
PARSERCOVERAGE_CASE();
|
|
if(AddText(pchStartField, 2, FALSE) == ecNoError)
|
|
pstate->sDest = destFieldInstruction;
|
|
break;
|
|
|
|
case tokenStartGroup: // Save current state by
|
|
PARSERCOVERAGE_CASE(); // pushing it onto stack
|
|
HandleStartGroup();
|
|
if (_fNoRTFtoken)
|
|
{
|
|
// Hack Alert !!!!! For 1.0 compatibility to allow no \rtf token.
|
|
_fNoRTFtoken = FALSE;
|
|
pstate = _pstateStackTop;
|
|
goto rtf;
|
|
}
|
|
break;
|
|
|
|
case tokenEndGroup:
|
|
PARSERCOVERAGE_CASE();
|
|
HandleFieldEndGroup(); // Special end group handling for \field
|
|
HandleEndGroup(); // Restore save state by
|
|
break; // popping stack
|
|
|
|
case tokenOptionalDestination: // (see case tokenUnknown)
|
|
PARSERCOVERAGE_CASE();
|
|
break;
|
|
|
|
case tokenNullDestination: // Found a destination whose group
|
|
PARSERCOVERAGE_CASE(); // should be skipped
|
|
// tokenNullDestination triggers a loss notification here for...
|
|
// Footer related tokens - "footer", "footerf", "footerl", "footerr",
|
|
// "footnote", "ftncn", "ftnsep", "ftnsepc"
|
|
// Header related tokens - "header", "headerf", "headerl", "headerr"
|
|
// Table of contents - "tc"
|
|
// Index entries - "xe"
|
|
|
|
CheckNotifyLowFiRTF();
|
|
// V-GUYB: PWord Converter requires loss notification.
|
|
#ifdef REPORT_LOSSAGE
|
|
if(!(_dwFlags & SFF_SELECTION)) // SFF_SELECTION is set if any kind of paste is being done.
|
|
{
|
|
((LOST_COOKIE*)(_pes->dwCookie))->bLoss = TRUE;
|
|
}
|
|
#endif // REPORT_LOSSAGE
|
|
|
|
goto skip_group;
|
|
|
|
case tokenUnknownKeyword:
|
|
PARSERCOVERAGE_CASE();
|
|
if(_tokenLast == tokenOptionalDestination)
|
|
goto skip_group;
|
|
break;
|
|
|
|
|
|
//-------------------------- Text Control Words --------------------------------
|
|
|
|
case tokenUnicode: // \u N
|
|
PARSERCOVERAGE_CASE();
|
|
HandleUN(pstate);
|
|
break;
|
|
|
|
case tokenUnicodeCharByteCount: // \uc N
|
|
PARSERCOVERAGE_CASE();
|
|
if(IN_RANGE(1, iParam, 2))
|
|
pstate->cbSkipForUnicodeMax = iParam;
|
|
break;
|
|
|
|
case tokenText: // Lexer concludes tokenText
|
|
case tokenASCIIText:
|
|
PARSERCOVERAGE_CASE();
|
|
HandleTextToken(pstate);
|
|
break;
|
|
|
|
// \ltrmark, \rtlmark, \zwj, and \zwnj are translated directly into
|
|
// their Unicode values. \ltrmark and \rtlmark cause no further
|
|
// processing here because we assume that the current font has the
|
|
// CharSet needed to identify the direction.
|
|
case tokenLToRDocument: // \ltrdoc
|
|
PARSERCOVERAGE_CASE();
|
|
_bDocType = DT_LTRDOC;
|
|
break;
|
|
|
|
case tokenRToLDocument: // \rtldoc
|
|
PARSERCOVERAGE_CASE();
|
|
_bDocType = DT_RTLDOC;
|
|
_ped->OrCharFlags(FRTL);
|
|
break;
|
|
|
|
|
|
//--------------------------Shape Control Words---------------------------------
|
|
|
|
case tokenShape: // \shp
|
|
if(!pstate->fBackground)
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
pstate->fShape = TRUE;
|
|
_dwFlagsShape = 0;
|
|
break;
|
|
|
|
case tokenShapeName: // \sn name
|
|
pstate->sDest = destShapeName;
|
|
break;
|
|
|
|
case tokenShapeValue: // \sv value
|
|
pstate->sDest = destShapeValue;
|
|
break;
|
|
|
|
case tokenShapeWrap: // \shpwr N
|
|
if(iParam == 2)
|
|
_dwFlagsShape |= REO_WRAPTEXTAROUND;
|
|
break;
|
|
|
|
case tokenPositionRight: // \posxr
|
|
_dwFlagsShape |= REO_ALIGNTORIGHT;
|
|
break;
|
|
|
|
|
|
//------------------------- Object Control Words --------------------------------
|
|
|
|
case tokenObject: // \object
|
|
PARSERCOVERAGE_CASE();
|
|
// V-GUYB: PWord Converter requires loss notification.
|
|
#ifdef REPORT_LOSSAGE
|
|
if(!(_dwFlags & SFF_SELECTION)) // SFF_SELECTION is set if any kind of paste is being done.
|
|
{
|
|
((LOST_COOKIE*)(_pes->dwCookie))->bLoss = TRUE;
|
|
}
|
|
#endif // REPORT_LOSSAGE
|
|
|
|
// Assume that the object failed to load until proven otherwise
|
|
// by RTFRead::ObjectReadFromEditStream
|
|
// This works for both:
|
|
// - an empty \objdata tag
|
|
// - a non-existent \objdata tag
|
|
_fFailedPrevObj = TRUE;
|
|
|
|
case tokenPicture: // \pict
|
|
PARSERCOVERAGE_CASE();
|
|
|
|
pstate->sDest = (SHORT)(_token == tokenPicture ? destPicture : destObject);
|
|
|
|
FreeRtfObject();
|
|
_prtfObject = (RTFOBJECT *) PvAlloc(sizeof(RTFOBJECT), GMEM_ZEROINIT);
|
|
if(!_prtfObject)
|
|
goto OutOfRAM;
|
|
_prtfObject->xScale = _prtfObject->yScale = 100;
|
|
_prtfObject->cBitsPerPixel = 1;
|
|
_prtfObject->cColorPlanes = 1;
|
|
_prtfObject->szClass = NULL;
|
|
_prtfObject->szName = NULL;
|
|
_prtfObject->sType = -1;
|
|
break;
|
|
|
|
case tokenObjectEBookImage:
|
|
// Added by VikramM for E-Book
|
|
//
|
|
_prtfObject->sType = ROT_EBookImage;
|
|
break;
|
|
|
|
case tokenObjectEmbedded: // \objemb
|
|
case tokenObjectLink: // \objlink
|
|
case tokenObjectAutoLink: // \objautlink
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->sType = (SHORT)(_token - tokenObjectEmbedded + ROT_Embedded);
|
|
break;
|
|
|
|
case tokenObjectMacSubscriber: // \objsub
|
|
case tokenObjectMacPublisher: // \objpub
|
|
case tokenObjectMacICEmbedder:
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->sType = ROT_MacEdition;
|
|
break;
|
|
|
|
case tokenWidth: // \picw N or \objw N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->xExt = iParam;
|
|
break;
|
|
|
|
case tokenHeight: // \pic N or \objh N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->yExt = iParam;
|
|
break;
|
|
|
|
case tokenObjectSetSize: // \objsetsize
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->fSetSize = TRUE;
|
|
break;
|
|
|
|
case tokenScaleX: // \picscalex N or \objscalex N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->xScale = iParam;
|
|
break;
|
|
|
|
case tokenScaleY: // \picscaley N or \objscaley N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->yScale = iParam;
|
|
break;
|
|
|
|
case tokenCropLeft: // \piccropl or \objcropl
|
|
case tokenCropTop: // \piccropt or \objcropt
|
|
case tokenCropRight: // \piccropr or \objcropr
|
|
case tokenCropBottom: // \piccropb or \objcropb
|
|
PARSERCOVERAGE_CASE();
|
|
*((LONG *)&_prtfObject->rectCrop
|
|
+ (_token - tokenCropLeft)) = iParam;
|
|
break;
|
|
|
|
case tokenObjectClass: // \objclass
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destObjectClass;
|
|
break;
|
|
|
|
case tokenObjectName: // \objname
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destObjectName;
|
|
break;
|
|
|
|
case tokenObjectResult: // \result
|
|
PARSERCOVERAGE_CASE();
|
|
if(_fMac || // If it's Mac stuff, we don't
|
|
_prtfObject->sType==ROT_MacEdition ||// understand the data, or if
|
|
_fFailedPrevObj || _fNeedPres) // we need an obj presentation,
|
|
{
|
|
pstate->sDest = destRTF; // use the object results
|
|
break;
|
|
}
|
|
goto skip_group;
|
|
|
|
case tokenObjectData: // \objdata
|
|
PARSERCOVERAGE_CASE();
|
|
pstate->sDest = destObjectData;
|
|
if(_prtfObject->sType==ROT_MacEdition) // It's Mac stuff so just
|
|
goto skip_group; // throw away the data
|
|
break;
|
|
|
|
case tokenPictureWindowsMetafile: // \wmetafile
|
|
#ifdef NOMETAFILES
|
|
goto skip_group;
|
|
#endif NOMETAFILES
|
|
|
|
case tokenPngBlip: // \pngblip
|
|
case tokenJpegBlip: // \jpegblip
|
|
case tokenPictureWindowsDIB: // \dibitmap N
|
|
case tokenPictureWindowsBitmap: // \wbitmap N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->sType = (SHORT)(_token - tokenPictureWindowsBitmap + ROT_Bitmap);
|
|
_prtfObject->sPictureType = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenBitmapBitsPerPixel: // \wbmbitspixel N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->cBitsPerPixel = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenBitmapNumPlanes: // \wbmplanes N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->cColorPlanes = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenBitmapWidthBytes: // \wbmwidthbytes N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->cBytesPerLine = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenDesiredWidth: // \picwgoal N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->xExtGoal = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenDesiredHeight: // \pichgoal N
|
|
PARSERCOVERAGE_CASE();
|
|
_prtfObject->yExtGoal = (SHORT)iParam;
|
|
break;
|
|
|
|
case tokenBinaryData: // \bin N
|
|
PARSERCOVERAGE_CASE();
|
|
|
|
// Update OleGet function
|
|
RTFReadOLEStream.lpstbl->Get =
|
|
(DWORD (CALLBACK* )(LPOLESTREAM, void FAR*, DWORD))
|
|
RTFGetBinaryDataFromStream;
|
|
_cbBinLeft = iParam; // Set data length
|
|
switch (pstate->sDest)
|
|
{
|
|
case destObjectData:
|
|
_fFailedPrevObj = !ObjectReadFromEditStream();
|
|
break;
|
|
|
|
case destPicture:
|
|
StaticObjectReadFromEditStream(iParam);
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE, "Binary data hit but don't know where to put it");
|
|
}
|
|
// Restore OleGet function
|
|
RTFReadOLEStream.lpstbl->Get =
|
|
(DWORD (CALLBACK* )(LPOLESTREAM, void FAR*, DWORD))
|
|
RTFGetFromStream;
|
|
break;
|
|
|
|
case tokenObjectDataValue:
|
|
PARSERCOVERAGE_CASE();
|
|
if(_prtfObject->sType != ROT_EBookImage) // Added by VikramM for E-Book
|
|
{
|
|
// Normal processing
|
|
_fFailedPrevObj = !ObjectReadFromEditStream();
|
|
}
|
|
else
|
|
{
|
|
// Do the Ebook Image callback here and set the _prtfObject size here
|
|
// Don't need to read the image data at this point, we just want to
|
|
// do a callback at a later point to have the E-Book shell render the image
|
|
_fFailedPrevObj = !ObjectReadEBookImageInfoFromEditStream();
|
|
}
|
|
goto EndOfObjectStream;
|
|
|
|
case tokenPictureDataValue:
|
|
PARSERCOVERAGE_CASE();
|
|
StaticObjectReadFromEditStream();
|
|
EndOfObjectStream:
|
|
if(!SkipToEndOfGroup())
|
|
HandleEndGroup();
|
|
break;
|
|
|
|
case tokenObjectPlaceholder:
|
|
PARSERCOVERAGE_CASE();
|
|
if(_ped->GetEventMask() & ENM_OBJECTPOSITIONS)
|
|
{
|
|
if(!_pcpObPos)
|
|
{
|
|
_pcpObPos = (LONG *)PvAlloc(sizeof(ULONG) * cobPosInitial, GMEM_ZEROINIT);
|
|
if(!_pcpObPos)
|
|
{
|
|
_ecParseError = ecNoMemory;
|
|
break;
|
|
}
|
|
_cobPosFree = cobPosInitial;
|
|
_cobPos = 0;
|
|
}
|
|
if(_cobPosFree-- <= 0)
|
|
{
|
|
const int cobPosNew = _cobPos + cobPosChunk;
|
|
LPVOID pv;
|
|
|
|
pv = PvReAlloc(_pcpObPos, sizeof(ULONG) * cobPosNew);
|
|
if(!pv)
|
|
{
|
|
_ecParseError = ecNoMemory;
|
|
break;
|
|
}
|
|
_pcpObPos = (LONG *)pv;
|
|
_cobPosFree = cobPosChunk - 1;
|
|
}
|
|
_pcpObPos[_cobPos++] = _prg->GetCp();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PARSERCOVERAGE_DEFAULT();
|
|
if(pstate->sDest != destFieldInstruction && // Values outside token
|
|
(DWORD)(_token - tokenMin) > // range are treated
|
|
(DWORD)(tokenMax - tokenMin)) // as Unicode chars
|
|
{
|
|
// 1.0 mode doesn't use Unicode bullets nor smart quotes
|
|
if (_ped->Get10Mode() && IN_RANGE(LQUOTE, _token, RDBLQUOTE))
|
|
{
|
|
if (_token == LQUOTE || _token == RQUOTE)
|
|
_token = L'\'';
|
|
else if (_token == LDBLQUOTE || _token == RDBLQUOTE)
|
|
_token = L'\"';
|
|
}
|
|
|
|
if(!IsLowMergedCell())
|
|
HandleChar(_token);
|
|
}
|
|
#if defined(DEBUG) && !defined(NOFULLDEBUG)
|
|
else
|
|
{
|
|
if(GetProfileIntA("RICHEDIT DEBUG", "RTFCOVERAGE", 0))
|
|
{
|
|
CHAR *pszKeyword = PszKeywordFromToken(_token);
|
|
CHAR szBuf[256];
|
|
|
|
sprintf(szBuf, "CRTFRead::HandleToken(): Token not processed - token = %d, %s%s%s",
|
|
_token,
|
|
"keyword = ",
|
|
pszKeyword ? "\\" : "<unknown>",
|
|
pszKeyword ? pszKeyword : "");
|
|
|
|
AssertSz(0, szBuf);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
TRACEERRSZSC("HandleToken()", - _ecParseError);
|
|
return _ecParseError;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::IsLowMergedCell()
|
|
*
|
|
* @mfunc
|
|
* Return TRUE iff _prg is currently in a low merged table cell. Note
|
|
* that RichEdit can't insert any text into a low merged cell, but
|
|
* Word's RTF sometimes attempts to, e.g., {\listtext...} ignored
|
|
* by Word can be (erroneously) emitted for insertion into these cells.
|
|
* Hence we discard such insertions.
|
|
*
|
|
* @rdesc
|
|
* Return TRUE iff _prg is currently in a low merged table cell
|
|
*/
|
|
BOOL CRTFRead::IsLowMergedCell()
|
|
{
|
|
if(!_bTableLevel)
|
|
return FALSE;
|
|
|
|
CELLPARMS *pCellParms = (CELLPARMS *)&_rgxCell[0];
|
|
|
|
return IsLowCell(pCellParms[_iCell].uCell);
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::Pard(pstate)
|
|
*
|
|
* @mfunc
|
|
* Reset paragraph and pstate properties to default values
|
|
*/
|
|
void CRTFRead::Pard(
|
|
STATE *pstate)
|
|
{
|
|
if(IN_RANGE(destColorTable, pstate->sDest, destPicture))
|
|
{
|
|
_ecParseError = ecAbort;
|
|
return;
|
|
}
|
|
BYTE bT = _PF._bOutlineLevel; // Save outline level
|
|
_PF.InitDefault(_bDocType == DT_RTLDOC ? PFE_RTLPARA : 0);
|
|
// Reset para formatting
|
|
pstate->fBullet = FALSE;
|
|
pstate->sIndentNumbering = 0;
|
|
_cTab = 0; // No tabs defined
|
|
_bTabLeader = 0;
|
|
_bTabType = 0;
|
|
_bBorder = 0;
|
|
_fStartRow = FALSE;
|
|
_PF._bOutlineLevel = (BYTE)(bT | 1);
|
|
_dwMaskPF = PFM_ALLRTF;
|
|
_dwMaskPF2 = PFM2_TABLEROWSHIFTED;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::DelimitRow(szRowDelimiter)
|
|
*
|
|
* @mfunc
|
|
* Insert start-of-row or end-of-row paragraph with current table
|
|
* properties
|
|
*/
|
|
void CRTFRead::DelimitRow(
|
|
WCHAR *szRowDelimiter) //@parm Delimit text to insert
|
|
{
|
|
if(!_ped->_pdp->IsMultiLine()) // No tables in single line
|
|
{ // controls
|
|
_ecParseError = ecTruncateAtCRLF;
|
|
return;
|
|
}
|
|
|
|
LONG nTableIndex = _bTableLevel;
|
|
if(szRowDelimiter == szRowEnd)
|
|
{
|
|
if(!_iCell) // Bad RTF: \row with no \cell,
|
|
HandleCell(); // so fake one
|
|
nTableIndex--;
|
|
}
|
|
if(nTableIndex + _bTableLevelIP >= MAXTABLENEST)
|
|
{
|
|
if(szRowDelimiter == szRowEnd) // Maintain _bTableLevel
|
|
_bTableLevel--;
|
|
else
|
|
_bTableLevel++;
|
|
_token = tokenEndParagraph;
|
|
HandleEndOfPara();
|
|
return;
|
|
}
|
|
if(szRowDelimiter == szRowStart && _prg->GetCp() && !_prg->_rpTX.IsAfterEOP())
|
|
{
|
|
_token = tokenEndParagraph;
|
|
HandleEndOfPara();
|
|
}
|
|
Assert(_pstateStackTop && _pstateStackTop->pPF);
|
|
|
|
// Add _PF diffs to *_pstateStackTop->pPF
|
|
if(!_pstateStackTop->AddPF(_PF, _bDocType, _dwMaskPF, _dwMaskPF2))
|
|
{
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
return;
|
|
}
|
|
|
|
DWORD dwMaskPF = _pstateStackTop->dwMaskPF; // Save PF for restoration
|
|
DWORD dwMaskPF2 = _pstateStackTop->dwMaskPF2; // Save PF for restoration
|
|
SHORT iTabs = -1;
|
|
CParaFormat PF = *_pstateStackTop->pPF; // on return
|
|
|
|
_PF.InitDefault(_fRTLRow ? PFE_RTLPARA : 0);
|
|
_fStartRow = FALSE;
|
|
_dwMaskPF = PFM_ALLRTF;
|
|
_dwMaskPF2 = 0;
|
|
if(_wBorderWidth) // Store any border info
|
|
{
|
|
_PF._dwBorderColor = _dwBorderColors;
|
|
_PF._wBorders = _wBorders;
|
|
_PF._wBorderSpace = _wBorderSpace;
|
|
_PF._wBorderWidth = _wBorderWidth;
|
|
_dwMaskPF |= PFM_BORDER;
|
|
}
|
|
|
|
_PF._bAlignment = _bAlignment; // Row alignment (no cell align)
|
|
_PF._dxStartIndent = _xRowOffset; // \trleft N
|
|
_PF._dxOffset = max(_dxCell, 10); // \trgaph N
|
|
_PF._dyLineSpacing = _dyRow; // \trrh N
|
|
_PF._wEffects |= PFE_TABLE | PFE_TABLEROWDELIMITER;
|
|
|
|
BOOL fHidden = _ped->GetCharFormat(_prg->Get_iFormat())->_dwEffects & CFE_HIDDEN;
|
|
_prg->_rpCF.AdjustBackward();
|
|
if(_prg->IsHidden())
|
|
{
|
|
CCharFormat CF;
|
|
CF._dwEffects = 0; // Don't hide EOP preceding TRD
|
|
_prg->BackupCRLF(CSC_NORMAL, TRUE);
|
|
_prg->SetCharFormat(&CF, 0, NULL, CFM_HIDDEN, 0);
|
|
CheckNotifyLowFiRTF(TRUE);
|
|
_prg->AdvanceCRLF(CSC_NORMAL, FALSE);
|
|
}
|
|
_prg->_rpCF.AdjustForward();
|
|
|
|
AssertSz(!_prg->GetCp() || IsEOP(_prg->GetPrevChar()),
|
|
"CRTFRead::DelimitRow: no EOP precedes TRD");
|
|
|
|
if(AddText(szRowDelimiter, 2, FALSE) != ecNoError)
|
|
goto cleanup;
|
|
|
|
if(!_bTableLevel && _PF._dxStartIndent < 50)// Move neg shifted table right
|
|
{ // (handles common default Word table)
|
|
_PF._wEffects |= PFE_TABLEROWSHIFTED;
|
|
_dwMaskPF2 |= PFM2_TABLEROWSHIFTED;
|
|
_PF._dxStartIndent += _dxCell + 50; // 50 gives room for left border
|
|
}
|
|
if(szRowDelimiter == szRowStart)
|
|
_bTableLevel++;
|
|
|
|
_PF._bTableLevel = _bTableLevel + _bTableLevelIP;
|
|
iTabs = Apply_PF();
|
|
if(szRowDelimiter == szRowStart)
|
|
{
|
|
if(_bTableLevel == 1)
|
|
_iTabsLevel1 = iTabs;
|
|
_rgTableState[nTableIndex]._iCell = _iCell;
|
|
_rgTableState[nTableIndex]._cCell = _cCell;
|
|
|
|
if(_token == tokenTableLevel)
|
|
_cCell = 0;
|
|
_iCell = 0;
|
|
|
|
if(!_cCell) // Cache if need to recompute row PF
|
|
_dwRowResolveFlags |= 1 << _bTableLevel;
|
|
}
|
|
else
|
|
{
|
|
Assert(szRowDelimiter == szRowEnd);
|
|
DWORD dwMask = 1 << _bTableLevel;
|
|
if(_dwRowResolveFlags & dwMask)
|
|
{ // Copy iPF over to corresponding
|
|
CPFRunPtr rpPF(*_prg); // row header
|
|
rpPF.ResolveRowStartPF();
|
|
_dwRowResolveFlags &= (dwMask - 1);
|
|
// Insert NOTACHARs for cells
|
|
LONG cp = _prg->GetCp(); // vert merged with cells above
|
|
LONG j = _cCell - 1;
|
|
CELLPARMS *pCellParms = (CELLPARMS *)&_rgxCell[0];
|
|
WCHAR szNOTACHAR[1] = {NOTACHAR};
|
|
|
|
_prg->Move(-2, FALSE); // Move before row-end delimiter
|
|
for(LONG i = _cCell; i--;) // and CELL mark
|
|
{
|
|
if(IsLowCell(pCellParms[i].uCell))
|
|
{
|
|
if(i != j)
|
|
_prg->Move(tomCell, i - j, NULL);
|
|
if(_prg->GetPrevChar() == CELL)
|
|
_prg->Move(-1, FALSE); // Backspace over CELL mark
|
|
Assert(_prg->_rpTX.GetChar() == CELL);
|
|
|
|
if(_prg->_rpTX.GetPrevChar() == NOTACHAR)
|
|
_prg->Move(-1, FALSE);
|
|
else
|
|
{
|
|
_prg->ReplaceRange(1, szNOTACHAR, NULL, SELRR_IGNORE, NULL, 0);
|
|
_prg->Move(-2, FALSE); // Backspace over NOTACHAR CELL combo
|
|
cp++;
|
|
}
|
|
j = i - 1;
|
|
}
|
|
}
|
|
_prg->SetCp(cp, FALSE); // Reposition rg after end-row delim
|
|
Assert(_prg->_rpTX.IsAfterTRD(ENDFIELD));
|
|
}
|
|
_bTableLevel--; // End of current row
|
|
_iCell = _rgTableState[nTableIndex]._iCell;
|
|
_cCell = _rgTableState[nTableIndex]._cCell;
|
|
|
|
if(!_bTableLevel)
|
|
_fStartRow = TRUE; // Tell AddText to start new row
|
|
} // unless \pard terminates it
|
|
_cpThisPara = _prg->GetCp(); // New para starts after CRLF
|
|
|
|
cleanup:
|
|
_PF = PF;
|
|
_dwMaskPF = dwMaskPF;
|
|
_dwMaskPF2 = dwMaskPF2;
|
|
|
|
if(fHidden) // Restore hidden property
|
|
{
|
|
_CF._dwEffects |= CFE_HIDDEN;
|
|
_dwMaskCF |= CFM_HIDDEN;
|
|
}
|
|
|
|
Assert(!(_PF._wEffects & PFE_TABLEROWDELIMITER));
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::InitializeTableRowParms()
|
|
*
|
|
* @mfunc
|
|
* Initialize table parms to no table state
|
|
*/
|
|
void CRTFRead::InitializeTableRowParms()
|
|
{
|
|
// Initialize table parms
|
|
_cCell = 0; // No table cells yet
|
|
_iCell = 0;
|
|
_fStartRow = FALSE;
|
|
_wBorderWidth = 0;
|
|
_bAlignment = PFA_LEFT;
|
|
_xRowOffset = 0;
|
|
_dxCell = 0;
|
|
_dyRow = 0;
|
|
_iTabsTable = -1;
|
|
}
|
|
|
|
/*
|
|
* CRTFRead::ReadRtf()
|
|
*
|
|
* @mfunc
|
|
* The range _prg is replaced by RTF data resulting from parsing the
|
|
* input stream _pes. The CRTFRead object assumes that the range is
|
|
* already degenerate (caller has to delete the range contents, if
|
|
* any, before calling this routine). Currently any info not used
|
|
* or supported by RICHEDIT is thrown away.
|
|
*
|
|
* @rdesc
|
|
* Number of chars inserted into text. 0 means none were inserted
|
|
* OR an error occurred.
|
|
*/
|
|
LONG CRTFRead::ReadRtf()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSRTFR, TRCSCOPEINTERN, "CRTFRead::ReadRtf");
|
|
|
|
LONG cpFirst;
|
|
LONG cpFirstInPara;
|
|
CTxtRange * prg = _prg;
|
|
STATE * pstate;
|
|
|
|
cpFirst = _cpFirst = prg->GetCp();
|
|
|
|
if (!_cchMax)
|
|
{
|
|
// At text limit already, forget it.
|
|
_ecParseError = ecTextMax;
|
|
goto Quit;
|
|
}
|
|
|
|
if(!InitLex())
|
|
goto Quit;
|
|
|
|
TESTPARSERCOVERAGE();
|
|
|
|
AssertSz(!prg->GetCch(),
|
|
"CRTFRead::ReadRtf: range must be deleted");
|
|
|
|
if(!(_dwFlags & SFF_SELECTION))
|
|
{
|
|
// SFF_SELECTION is set if any kind of paste is being done, i.e.,
|
|
// not just that using the selection. If it isn't set, data is
|
|
// being streamed in and we allow this to reset the doc params
|
|
if(_ped->InitDocInfo() != NOERROR)
|
|
{
|
|
_ecParseError = ecNoMemory;
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
prg->SetIgnoreFormatUpdate(TRUE);
|
|
|
|
_szUnicode = (WCHAR *)PvAlloc(cachTextMax * sizeof(WCHAR), GMEM_ZEROINIT);
|
|
if(!_szUnicode) // Allocate space for Unicode conversions
|
|
{
|
|
_ped->GetCallMgr()->SetOutOfMemory();
|
|
_ecParseError = ecNoMemory;
|
|
goto CleanUp;
|
|
}
|
|
_cchUnicode = cachTextMax;
|
|
|
|
// Initialize per-read variables
|
|
_nCodePage = (_dwFlags & SF_USECODEPAGE)
|
|
? (_dwFlags >> 16) : INVALID_CODEPAGE;
|
|
|
|
// Populate _PF with initial paragraph formatting properties
|
|
_PF = *prg->GetPF();
|
|
_dwMaskPF = PFM_ALLRTF; // Setup initial MaskPF
|
|
_PF._iTabs = -1; // In case it's not -1
|
|
if(_PF.IsTableRowDelimiter()) // Do _not_ insert with this property!
|
|
{
|
|
if(prg->_rpTX.IsAtTRD(ENDFIELD))
|
|
{
|
|
prg->AdvanceCRLF(CSC_NORMAL, FALSE);// Bypass table row-end delimiter
|
|
cpFirst = prg->GetCp(); // Update value
|
|
_PF = *prg->GetPF(); // Might still be row-start delimiter
|
|
_PF._iTabs = -1;
|
|
Assert(!prg->_rpTX.IsAtTRD(ENDFIELD));
|
|
}
|
|
if(prg->_rpTX.IsAtTRD(STARTFIELD))
|
|
{
|
|
// REVIEW: this if can probably be omitted now since the caller calls
|
|
// DeleteWithTRDCheck()
|
|
_ecParseError = ecGeneralFailure;
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
_bTableLevelIP = _PF._bTableLevel; // Save table level of insertion pt
|
|
AssertSz(_bTableLevelIP >= 0, "CRTFRead::ReadRtf: illegal table level");
|
|
|
|
// V-GUYB: PWord Converter requires loss notification.
|
|
#ifdef REPORT_LOSSAGE
|
|
if(!(_dwFlags & SFF_SELECTION)) // SFF_SELECTION is set if any
|
|
{ // kind of paste is being done
|
|
((LOST_COOKIE*)(_pes->dwCookie))->bLoss = FALSE;
|
|
}
|
|
#endif // REPORT_LOSSAGE
|
|
|
|
// Valid RTF files start with "{\rtf", "{urtf", or "{\pwd"
|
|
GetChar(); // Fill input buffer
|
|
UngetChar(); // Put char back
|
|
if(!IsRTF((char *)_pchRTFCurrent, _pchRTFEnd - _pchRTFCurrent)) // Is it RTF?
|
|
{ // No
|
|
if (_ped->Get10Mode())
|
|
_fNoRTFtoken = TRUE;
|
|
else
|
|
{
|
|
_ecParseError = ecUnexpectedToken; // Signal bad file
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
// If initial cp follows EOP, use it for _cpThisPara. Else
|
|
// search for start of para containing the initial cp.
|
|
_cpThisPara = prg->GetCp();
|
|
if(!prg->_rpTX.IsAfterEOP())
|
|
{
|
|
CTxtPtr tp(prg->_rpTX);
|
|
tp.FindEOP(tomBackward);
|
|
_cpThisPara = tp.GetCp();
|
|
}
|
|
cpFirstInPara = _cpThisPara; // Backup to start of para before
|
|
// parsing
|
|
while ( TokenGetToken() != tokenEOF && // Process tokens
|
|
_token != tokenError &&
|
|
!HandleToken() &&
|
|
_pstateStackTop )
|
|
;
|
|
|
|
if(_ecParseError == ecAbort) // Really vile error: delete anything
|
|
{ // that was inserted
|
|
prg->Set(prg->GetCp(), prg->GetCp() - cpFirst);
|
|
prg->ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL,
|
|
RR_NO_LP_CHECK | RR_NO_TRD_CHECK | RR_NO_CHECK_TABLE_SEL);
|
|
goto CleanUp;
|
|
}
|
|
if(_bTableLevel) // Whoops! still in middle of table
|
|
{
|
|
LONG cpMin;
|
|
prg->_rpPF.AdjustBackward(); // Be sure to get preceding level
|
|
prg->FindRow(&cpMin, NULL, _bTableLevelIP + 1);// Find beginning of row and
|
|
if(cpMin == prg->GetCp()) // delete from there to here
|
|
{ // No table formatting yet
|
|
LONG ch; // Just delete back to STARTFIELD
|
|
CTxtPtr tp(prg->_rpTX);
|
|
while((ch = tp.PrevChar()) && ch != STARTFIELD)
|
|
;
|
|
cpMin = tp.GetCp();
|
|
}
|
|
cpMin = max(cpMin, _cpFirst);
|
|
prg->Set(prg->GetCp(), prg->GetCp() - cpMin);
|
|
prg->ReplaceRange(0, NULL, NULL, SELRR_IGNORE, NULL,
|
|
RR_NO_LP_CHECK | RR_NO_TRD_CHECK | RR_NO_CHECK_TABLE_SEL);
|
|
#ifdef DEBUG
|
|
prg->_rpTX.MoveGapToEndOfBlock();
|
|
#endif
|
|
}
|
|
_cCell = _iCell = 0;
|
|
|
|
prg->SetIgnoreFormatUpdate(FALSE); // Enable range _iFormat updates
|
|
prg->Update_iFormat(-1); // Update _iFormat to CF
|
|
// at current active end
|
|
if(!(_dwFlags & SFF_SELECTION)) // RTF applies to document:
|
|
{ // update CDocInfo
|
|
// Apply char and para formatting of
|
|
// final text run to final CR
|
|
if (prg->GetCp() == _ped->GetAdjustedTextLength() &&
|
|
!(_dwMaskPF & (PFM_TABLEROWDELIMITER | PFM_TABLE)))
|
|
{
|
|
// REVIEW: we need to think about what para properties should
|
|
// be transferred here. E.g., borders were being transferred
|
|
// incorrectly
|
|
_dwMaskPF &= ~(PFM_BORDER | PFM_SHADING);
|
|
Apply_PF();
|
|
prg->ExtendFormattingCRLF();
|
|
}
|
|
|
|
// Update the per-document information from the RTF read
|
|
CDocInfo *pDocInfo = _ped->GetDocInfoNC();
|
|
|
|
if(!pDocInfo)
|
|
{
|
|
Assert(FALSE); // Should be allocated by
|
|
_ecParseError = ecNoMemory; // earlier call in this function
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (ecNoError == _ecParseError) // If range end EOP wasn't
|
|
prg->DeleteTerminatingEOP(NULL); // deleted and new text
|
|
// ends with an EOP, delete that EOP
|
|
pDocInfo->_wCpg = (WORD)(_nCodePage == INVALID_CODEPAGE ?
|
|
tomInvalidCpg : _nCodePage);
|
|
if (pDocInfo->_wCpg == CP_UTF8)
|
|
pDocInfo->_wCpg = 1252;
|
|
|
|
_ped->SetDefaultLCID(_sDefaultLanguage == INVALID_LANGUAGE ?
|
|
tomInvalidLCID :
|
|
MAKELCID(_sDefaultLanguage, SORT_DEFAULT));
|
|
|
|
_ped->SetDefaultLCIDFE(_sDefaultLanguageFE == INVALID_LANGUAGE ?
|
|
tomInvalidLCID :
|
|
MAKELCID(_sDefaultLanguageFE, SORT_DEFAULT));
|
|
|
|
_ped->SetDefaultTabStop(TWIPS_TO_FPPTS(_sDefaultTabWidth));
|
|
_ped->SetDocumentType(_bDocType);
|
|
}
|
|
|
|
if(_ped->IsComplexScript() && prg->GetCp() > cpFirstInPara)
|
|
{
|
|
Assert(!prg->GetCch());
|
|
LONG cpSave = prg->GetCp();
|
|
LONG cpLastInPara = cpSave;
|
|
|
|
if(_ped->IsBiDi() && !prg->_rpTX.IsAtEOP())
|
|
{
|
|
CTxtPtr tp(prg->_rpTX);
|
|
tp.FindEOP(tomForward);
|
|
cpLastInPara = tp.GetCp();
|
|
prg->Move(cpLastInPara - cpSave, FALSE);
|
|
}
|
|
// Itemize from the start of paragraph to be inserted till the end of
|
|
// paragraph inserting. We need to cover all affected paragraphs because
|
|
// paragraphs we're playing could possibly in conflict direction. Think
|
|
// about the case that the range covers one LTR para and one RTL para, then
|
|
// the inserting text covers one RTL and one LTR. Both paragraphs' direction
|
|
// could have been changed after this insertion.
|
|
prg->ItemizeReplaceRange(cpLastInPara - cpFirstInPara, 0, NULL,
|
|
_ped->IsBiDi() && !_fNon0CharSet);
|
|
if (cpLastInPara != cpSave)
|
|
prg->SetCp(cpSave, FALSE);
|
|
}
|
|
|
|
CleanUp:
|
|
FreeRtfObject();
|
|
|
|
pstate = _pstateStackTop;
|
|
if(pstate) // Illegal RTF file. Release
|
|
{ // unreleased format indices
|
|
if(ecNoError == _ecParseError) // It's only an overflow if no
|
|
_ecParseError = ecStackOverflow; // other error has occurred
|
|
|
|
if(_ecParseError != ecAbort)
|
|
HandleFieldEndGroup(); // Cleanup possible partial field
|
|
while(pstate->pstatePrev)
|
|
{
|
|
pstate = pstate->pstatePrev;
|
|
ReleaseFormats(pstate->iCF, -1);
|
|
}
|
|
}
|
|
|
|
pstate = _pstateLast;
|
|
if(pstate)
|
|
{
|
|
while(pstate->pstatePrev) // Free all but first STATE
|
|
{
|
|
pstate->DeletePF();
|
|
pstate = pstate->pstatePrev;
|
|
FreePv(pstate->pstateNext);
|
|
}
|
|
pstate->DeletePF();
|
|
}
|
|
Assert(_PF._iTabs == -1);
|
|
FreePv(pstate); // Free first STATE
|
|
FreePv(_szUnicode);
|
|
|
|
Quit:
|
|
DeinitLex();
|
|
|
|
if(_pcpObPos)
|
|
{
|
|
if((_ped->GetEventMask() & ENM_OBJECTPOSITIONS) && _cobPos > 0)
|
|
{
|
|
OBJECTPOSITIONS obpos;
|
|
|
|
obpos.cObjectCount = _cobPos;
|
|
obpos.pcpPositions = _pcpObPos;
|
|
|
|
if (_ped->Get10Mode())
|
|
{
|
|
LONG *pcpPositions = _pcpObPos;
|
|
|
|
for (LONG i = 0; i < _cobPos; i++, pcpPositions++)
|
|
*pcpPositions = _ped->GetAcpFromCp(*pcpPositions);
|
|
}
|
|
_ped->TxNotify(EN_OBJECTPOSITIONS, &obpos);
|
|
}
|
|
|
|
FreePv(_pcpObPos);
|
|
_pcpObPos = NULL;
|
|
}
|
|
|
|
// transcribed from winerror.h
|
|
#define ERROR_HANDLE_EOF 38L
|
|
|
|
// FUTURE(BradO): We should devise a direct mapping from our error codes
|
|
// to Win32 error codes. In particular our clients are
|
|
// not expecting the error code produced by:
|
|
// _pes->dwError = (DWORD) -(LONG) _ecParseError;
|
|
if(_ecParseError)
|
|
{
|
|
AssertSz(_ecParseError >= 0,
|
|
"Parse error is negative");
|
|
|
|
if(_ecParseError == ecTextMax)
|
|
{
|
|
_ped->GetCallMgr()->SetMaxText();
|
|
_pes->dwError = (DWORD)STG_E_MEDIUMFULL;
|
|
}
|
|
if(_ecParseError == ecUnexpectedEOF)
|
|
_pes->dwError = (DWORD)HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
|
|
|
|
if(!_pes->dwError && _ecParseError != ecTruncateAtCRLF)
|
|
_pes->dwError = (DWORD) -(LONG) _ecParseError;
|
|
|
|
#if defined(DEBUG)
|
|
TRACEERRSZSC("CchParse_", _pes->dwError);
|
|
if(ecNoError < _ecParseError && _ecParseError < ecLastError)
|
|
Tracef(TRCSEVERR, "Parse error: %s", rgszParseError[_ecParseError]);
|
|
#endif
|
|
}
|
|
if(cpFirst > _cpFirst && prg->GetCp() == cpFirst)
|
|
{
|
|
prg->SetCp(_cpFirst, FALSE); // Restore prg cp, since nothing inserted
|
|
return 0;
|
|
}
|
|
return prg->GetCp() - cpFirst;
|
|
}
|
|
|
|
|
|
/*
|
|
* CRTFRead::CpgInfoFromFaceName()
|
|
*
|
|
* @mfunc
|
|
* This routine fills in the TEXTFONT::bCharSet and TEXTFONT::nCodePage
|
|
* members of the TEXTFONT structure by querying the system for the
|
|
* metrics of the font described by TEXTFONT::szName.
|
|
*
|
|
* @rdesc
|
|
* A flag indicating whether the charset and codepage were successfully
|
|
* determined.
|
|
*/
|
|
BOOL CRTFRead::CpgInfoFromFaceName(
|
|
TEXTFONT *ptf)
|
|
{
|
|
// FUTURE(BradO): This code is a condensed version of a more sophisticated
|
|
// algorithm we use in font.cpp to second-guess the font-mapper.
|
|
// We should factor out the code from font.cpp for use here as well.
|
|
|
|
// Indicates that we've tried to obtain the cpg info from the system,
|
|
// so that after a failure we don't re-call this routine.
|
|
ptf->fCpgFromSystem = TRUE;
|
|
|
|
if(ptf->fNameIsDBCS)
|
|
{
|
|
// If fNameIsDBCS, we have high-ANSI characters in the facename, and
|
|
// no codepage with which to interpret them. The facename is gibberish,
|
|
// so don't waste time calling the system to match it.
|
|
return FALSE;
|
|
}
|
|
|
|
HDC hdc = _ped->TxGetDC();
|
|
if(!hdc)
|
|
return FALSE;
|
|
|
|
LOGFONT lf = {0};
|
|
TEXTMETRIC tm;
|
|
|
|
wcscpy(lf.lfFaceName, ptf->szName);
|
|
lf.lfCharSet = CharSetFromCharRep(CharRepFromCodePage(GetSystemDefaultCodePage()));
|
|
|
|
if(!GetTextMetrics(hdc, lf, tm) || tm.tmCharSet != lf.lfCharSet)
|
|
{
|
|
lf.lfCharSet = DEFAULT_CHARSET; // Doesn't match default sys
|
|
GetTextMetrics(hdc, lf, tm); // charset, so see what
|
|
} // DEFAULT_CHARSET gives
|
|
_ped->TxReleaseDC(hdc);
|
|
|
|
if(tm.tmCharSet != DEFAULT_CHARSET) // Got something, so use it
|
|
{
|
|
ptf->iCharRep = CharRepFromCharSet(tm.tmCharSet);
|
|
ptf->sCodePage = (SHORT)CodePageFromCharRep(ptf->iCharRep);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Including a source file, but we only want to compile this code for debug purposes
|
|
#if defined(DEBUG)
|
|
#include "rtflog.cpp"
|
|
#endif
|
|
|
|
|