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