|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: Rtf reader to convert the rtf content into Xaml content.
//
using System.Collections;
using System.Globalization;
using System.Text;
using System.IO;
using System.Windows.Media; // Color
using Microsoft.Win32; // Registry for font substitutes
using MS.Internal; // Invariant
using MS.Internal.Text;
namespace System.Windows.Documents
{
/// <summary>
/// Horizontal alignment
/// </summary>
internal enum HAlign
{
AlignLeft,
AlignRight,
AlignCenter,
AlignJustify,
AlignDefault
};
/// <summary>
/// Horizontal alignment
/// </summary>
internal enum VAlign
{
AlignTop,
AlignCenter,
AlignBottom,
};
/// <summary>
/// Flow direction
/// </summary>
internal enum DirState
{
DirDefault,
DirLTR,
DirRTL
};
internal enum FontSlot
{
LOCH = 0,
DBCH = 1,
HICH = 2
};
/// <summary>
/// Underline state
/// </summary>
internal enum ULState
{
ULNone,
ULNormal,
ULDot,
ULDash,
ULDashDot,
ULDashDotDot,
ULDouble,
ULHeavyWave,
ULLongDash,
ULThick,
ULThickDot,
ULThickDash,
ULThickDashDot,
ULThickDashDotDot,
ULThickLongDash,
ULDoubleWave,
ULWord,
ULWave
};
/// <summary>
/// Strikethrough state
/// </summary>
internal enum StrikeState
{
StrikeNone,
StrikeNormal,
StrikeDouble
};
/// <summary>
/// Document node type
/// WARNING: Keep in sync with array used by GetTagName
/// </summary>
internal enum DocumentNodeType
{
dnUnknown = 0,
dnText,
dnInline,
dnLineBreak,
dnHyperlink,
dnParagraph,
dnInlineUIContainer,
dnBlockUIContainer,
dnImage,
dnList,
dnListItem,
dnTable,
dnTableBody,
dnRow,
dnCell,
dnSection,
dnFigure,
dnFloater,
dnFieldBegin,
dnFieldEnd,
dnShape,
dnListText
};
/// <summary>
/// MarkerStyle
/// </summary>
internal enum MarkerStyle
{
MarkerNone = -1,
MarkerArabic = 0,
MarkerUpperRoman = 1,
MarkerLowerRoman = 2,
MarkerUpperAlpha = 3,
MarkerLowerAlpha = 4,
MarkerOrdinal = 5,
MarkerCardinal = 6,
MarkerBullet = 23,
MarkerHidden = 255 // pseudo-value for no list text
};
/// <summary>
/// Converters
/// </summary>
internal static class Converters
{
internal static double HalfPointToPositivePx(double halfPoint)
{
// Twip is 1/10 of half-point
// So convert it to Twip by multiplying halftPoint with 10
return TwipToPositivePx(halfPoint * 10);
}
internal static double TwipToPx(double twip)
{
return (twip / 1440f) * 96f;
}
internal static double TwipToPositivePx(double twip)
{
double px = (twip / 1440f) * 96f;
if (px < 0)
{
px = 0;
}
return px;
}
internal static double TwipToPositiveVisiblePx(double twip)
{
double px = (twip / 1440f) * 96f;
if (px < 0)
{
px = 0;
}
if (twip > 0.0 && px < 1.0)
{
px = 1.0;
}
return px;
}
internal static string TwipToPxString(double twip)
{
double px = TwipToPx(twip);
return px.ToString("f2", CultureInfo.InvariantCulture);
}
internal static string TwipToPositivePxString(double twip)
{
double px = TwipToPositivePx(twip);
return px.ToString("f2", CultureInfo.InvariantCulture);
}
internal static string TwipToPositiveVisiblePxString(double twip)
{
double px = TwipToPositiveVisiblePx(twip);
return px.ToString("f2", CultureInfo.InvariantCulture);
}
internal static double PxToPt(double px)
{
return (px / 96) * 72f;
}
internal static long PxToTwipRounded(double px)
{
double twip = (px / 96f) * 1440f;
if (twip < 0)
{
return (long)(twip - 0.5);
}
else
{
return (long)(twip + 0.5);
}
}
internal static long PxToHalfPointRounded(double px)
{
// Twip is 1/10 of half-point
double twip = (px / 96f) * 1440f;
double halfPoint = twip / 10;
if (halfPoint < 0)
{
return (long)(halfPoint - 0.5);
}
else
{
return (long)(halfPoint + 0.5);
}
}
internal static bool StringToDouble(ReadOnlySpan<char> s, ref double d)
{
bool ret = true;
d = 0.0;
try
{
d = double.Parse(s, provider: CultureInfo.InvariantCulture);
}
catch (System.OverflowException)
{
ret = false;
}
catch (System.FormatException)
{
ret = false;
}
return ret;
}
internal static bool StringToInt(ReadOnlySpan<char> s, ref int i)
{
bool ret = true;
i = 0;
try
{
i = int.Parse(s, provider: CultureInfo.InvariantCulture);
}
catch (System.OverflowException)
{
ret = false;
}
catch (System.FormatException)
{
ret = false;
}
return ret;
}
internal static string StringToXMLAttribute(string s)
{
if (!s.Contains('"'))
{
return s;
}
return s.Replace("\"", """);
}
internal static bool HexStringToInt(ReadOnlySpan<char> s, ref int i)
{
bool ret = true;
i = 0;
try
{
i = System.Int32.Parse(s, System.Globalization.NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
}
catch (System.OverflowException)
{
ret = false;
}
catch (System.FormatException)
{
ret = false;
}
return ret;
}
internal static string MarkerStyleToString(MarkerStyle ms)
{
switch (ms)
{
case MarkerStyle.MarkerArabic:
return "Decimal";
case MarkerStyle.MarkerUpperRoman:
return "UpperRoman";
case MarkerStyle.MarkerLowerRoman:
return "LowerRoman";
case MarkerStyle.MarkerUpperAlpha:
return "UpperLatin";
case MarkerStyle.MarkerLowerAlpha:
return "LowerLatin";
case MarkerStyle.MarkerOrdinal:
return "Decimal"; // Note no XAML support
case MarkerStyle.MarkerCardinal:
return "Decimal"; // Note no XAML support
case MarkerStyle.MarkerHidden:
return "None";
case MarkerStyle.MarkerBullet:
return "Disc";
default:
return "Decimal";
}
}
internal static string MarkerStyleToOldRTFString(MarkerStyle ms)
{
switch (ms)
{
case MarkerStyle.MarkerBullet:
// return "\\pnbidia";
// return "\\pnbidib";
return "\\pnlvlblt";
default:
case MarkerStyle.MarkerArabic:
// return "\\pndec";
return "\\pnlvlbody\\pndec";
case MarkerStyle.MarkerCardinal:
return "\\pnlvlbody\\pncard";
case MarkerStyle.MarkerUpperAlpha:
return "\\pnlvlbody\\pnucltr";
case MarkerStyle.MarkerUpperRoman:
return "\\pnlvlbody\\pnucrm";
case MarkerStyle.MarkerLowerAlpha:
return "\\pnlvlbody\\pnlcltr";
case MarkerStyle.MarkerLowerRoman:
return "\\pnlvlbody\\pnlcrm";
case MarkerStyle.MarkerOrdinal:
//return "\\pnlvlbody\\pnordt";
return "\\pnlvlbody\\pnord";
}
}
// Convert FG, BG and shading to produce color to fill with.
// Shading is in 100'ths of a percent (ie, 10000 is 100%)
// A value of zero for shading means use all CB.
// A value of 10000 for shading means use all CF.
// Intermediate values mean some combination of
internal static bool ColorToUse(ConverterState converterState, long cb, long cf, long shade, ref Color c)
{
ColorTableEntry entryCB = cb >= 0 ? converterState.ColorTable.EntryAt((int)cb) : null;
ColorTableEntry entryCF = cf >= 0 ? converterState.ColorTable.EntryAt((int)cf) : null;
// No shading
if (shade < 0)
{
if (entryCB == null)
{
return false;
}
else
{
c = entryCB.Color;
return true;
}
}
// Shading
else
{
Color cCB = entryCB != null ? entryCB.Color : Color.FromArgb(0xFF, 0, 0, 0);
Color cCF = entryCF != null ? entryCF.Color : Color.FromArgb(0xFF, 255, 255, 255);
// No color specifies means shading is treated as a grey intensity.
if (entryCF == null && entryCB == null)
{
c = Color.FromArgb(0xff,
(byte)(255 - (255 * shade / 10000)),
(byte)(255 - (255 * shade / 10000)),
(byte)(255 - (255 * shade / 10000)));
return true;
}
// Only CF means CF fades as shading goes from 10,000 to 0
else if (entryCB == null)
{
c = Color.FromArgb(0xff,
(byte)(cCF.R + ((255 - cCF.R) * (10000 - shade) / 10000)),
(byte)(cCF.G + ((255 - cCF.G) * (10000 - shade) / 10000)),
(byte)(cCF.B + ((255 - cCF.B) * (10000 - shade) / 10000)));
return true;
}
// Only CB means CB gets larger impact (from black ) as shading goes from 10000 to 0
else if (entryCF == null)
{
c = Color.FromArgb(0xff,
(byte)(cCB.R - (cCB.R * shade / 10000)),
(byte)(cCB.G - (cCB.G * shade / 10000)),
(byte)(cCB.B - (cCB.B * shade / 10000)));
return true;
}
// Both - need to mix colors
else
{
c = Color.FromArgb(0xff,
(byte)((cCB.R * (10000 - shade) / 10000) +
(cCF.R * shade / 10000)),
(byte)((cCB.G * (10000 - shade) / 10000) +
(cCF.G * shade / 10000)),
(byte)((cCB.B * (10000 - shade) / 10000) +
(cCF.B * shade / 10000)));
return true;
}
}
}
internal static string AlignmentToString(HAlign a, DirState ds)
{
switch (a)
{
case HAlign.AlignLeft:
return (ds != DirState.DirRTL) ? "Left" : "Right";
case HAlign.AlignRight:
return (ds != DirState.DirRTL) ? "Right" : "Left";
case HAlign.AlignCenter:
return "Center";
case HAlign.AlignJustify:
return "Justify";
case HAlign.AlignDefault:
default:
return "";
}
}
internal static string MarkerCountToString(MarkerStyle ms, long nCount)
{
StringBuilder sb = new StringBuilder();
if (nCount < 0)
{
nCount = 0;
}
switch (ms)
{
case MarkerStyle.MarkerUpperRoman:
case MarkerStyle.MarkerLowerRoman:
{
return MarkerRomanCountToString(sb, ms, nCount);
}
case MarkerStyle.MarkerLowerAlpha:
case MarkerStyle.MarkerUpperAlpha:
{
return MarkerAlphaCountToString(sb, ms, nCount);
}
case MarkerStyle.MarkerArabic:
case MarkerStyle.MarkerOrdinal:
case MarkerStyle.MarkerCardinal:
return nCount.ToString(CultureInfo.InvariantCulture);
case MarkerStyle.MarkerHidden:
case MarkerStyle.MarkerNone:
return "";
case MarkerStyle.MarkerBullet:
default:
return "\\'B7";
}
}
private static string MarkerRomanCountToString(StringBuilder sb, MarkerStyle ms, long nCount)
{
while (nCount >= 1000)
{
sb.Append('M');
nCount -= 1000;
}
// 100's
switch (nCount / 100)
{
case 9:
sb.Append("CM"); break;
case 8:
sb.Append("DCCC"); break;
case 7:
sb.Append("DCC"); break;
case 6:
sb.Append("DC"); break;
case 5:
sb.Append('D'); break;
case 4:
sb.Append("CD"); break;
case 3:
sb.Append("CCC"); break;
case 2:
sb.Append("CC"); break;
case 1:
sb.Append('C'); break;
case 0:
break;
}
nCount = nCount % 100;
// 10's
switch (nCount / 10)
{
case 9:
sb.Append("XC"); break;
case 8:
sb.Append("LXXX"); break;
case 7:
sb.Append("LXX"); break;
case 6:
sb.Append("LX"); break;
case 5:
sb.Append('L'); break;
case 4:
sb.Append("XL"); break;
case 3:
sb.Append("XXX"); break;
case 2:
sb.Append("XX"); break;
case 1:
sb.Append('X'); break;
case 0:
break;
}
nCount = nCount % 10;
// 1's
switch (nCount)
{
case 9:
sb.Append("IX"); break;
case 8:
sb.Append("VIII"); break;
case 7:
sb.Append("VII"); break;
case 6:
sb.Append("VI"); break;
case 5:
sb.Append('V'); break;
case 4:
sb.Append("IV"); break;
case 3:
sb.Append("III"); break;
case 2:
sb.Append("II"); break;
case 1:
sb.Append('I'); break;
case 0:
break;
}
if (ms == MarkerStyle.MarkerUpperRoman)
{
return sb.ToString();
}
else
{
return sb.ToString().ToLower(CultureInfo.InvariantCulture);
}
}
private static string MarkerAlphaCountToString(StringBuilder sb, MarkerStyle ms, long nCount)
{
int toThe1 = 26;
int toThe2 = 676;
int toThe3 = 17576;
int toThe4 = 456976;
int temp;
temp = 0;
while (nCount > toThe4 + toThe3 + toThe2 + toThe1)
{
temp++;
nCount -= toThe4;
}
if (temp > 0)
{
if (temp > 26) temp = 26;
sb.Append((char)('A' + (temp - 1)));
}
temp = 0;
while (nCount > toThe3 + toThe2 + toThe1)
{
temp++;
nCount -= toThe3;
}
if (temp > 0)
{
sb.Append((char)('A' + (temp - 1)));
}
temp = 0;
while (nCount > toThe2 + toThe1)
{
temp++;
nCount -= toThe2;
}
if (temp > 0)
{
sb.Append((char)('A' + (temp - 1)));
}
temp = 0;
while (nCount > toThe1)
{
temp++;
nCount -= toThe1;
}
if (temp > 0)
{
sb.Append((char)('A' + (temp - 1)));
}
sb.Append((char)('A' + (nCount - 1)));
if (ms == MarkerStyle.MarkerUpperAlpha)
{
return sb.ToString();
}
else
{
return sb.ToString().ToLower(CultureInfo.InvariantCulture);
}
}
// Convert byte to the hex data(0x3a ==> 0x33 and 0x61)
internal static void ByteToHex(byte byteData, out byte firstHexByte, out byte secondHexByte)
{
firstHexByte = (byte)((byteData >> 4) & 0x0f);
secondHexByte = (byte)(byteData & 0x0f);
// First hex digit
if (firstHexByte >= 0x00 && firstHexByte <= 0x09)
{
firstHexByte += 0x30;
}
else if (firstHexByte >= 0xa && firstHexByte <= 0xf)
{
firstHexByte += 'a' - 0xa;
}
// Second hex digit
if (secondHexByte >= 0x00 && secondHexByte <= 0x09)
{
secondHexByte += 0x30;
}
else if (secondHexByte >= 0xa && secondHexByte <= 0xf)
{
secondHexByte += 'a' - 0xa;
}
}
};
internal static class Validators
{
internal static bool IsValidFontSize(long fs)
{
return fs >= 0 && fs <= 0x7FFF;
}
internal static bool IsValidWidthType(long wt)
{
return wt >= 0 && wt <= 3;
}
internal static long MakeValidShading(long s)
{
if (s > 10000) s = 10000;
return s;
}
internal static long MakeValidBorderWidth(long w)
{
// Word's UI only supports values from 0 to 120. But it will actually render larger
// values, so let's maintain them through the converter (but still have some kind of limit).
if (w < 0)
{
w = 0;
}
if (w > 1440)
{
w = 1440;
}
return w;
}
};
/// <summary>
/// FormatState
/// </summary>
internal class FormatState
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal FormatState()
{
// Other settings
_dest = RtfDestination.DestNormal;
_stateSkip = 1;
// Font settings
SetCharDefaults();
// Para settings
SetParaDefaults();
// Row settings
SetRowDefaults();
}
internal FormatState(FormatState formatState)
{
// Font settings
Bold = formatState.Bold;
Italic = formatState.Italic;
Engrave = formatState.Engrave;
Shadow = formatState.Shadow;
SCaps = formatState.SCaps;
Outline = formatState.Outline;
Super = formatState.Super;
Sub = formatState.Sub;
SuperOffset = formatState.SuperOffset;
FontSize = formatState.FontSize;
Font = formatState.Font;
CodePage = formatState.CodePage;
CF = formatState.CF;
CB = formatState.CB;
DirChar = formatState.DirChar;
UL = formatState.UL;
Strike = formatState.Strike;
Expand = formatState.Expand;
Lang = formatState.Lang;
LangFE = formatState.LangFE;
LangCur = formatState.LangCur;
FontSlot = formatState.FontSlot;
// Para settings
SB = formatState.SB;
SA = formatState.SA;
FI = formatState.FI;
RI = formatState.RI;
LI = formatState.LI;
SL = formatState.SL;
SLMult = formatState.SLMult;
HAlign = formatState.HAlign;
ILVL = formatState.ILVL;
ITAP = formatState.ITAP;
ILS = formatState.ILS;
DirPara = formatState.DirPara;
CFPara = formatState.CFPara;
CBPara = formatState.CBPara;
ParaShading = formatState.ParaShading;
Marker = formatState.Marker;
IsContinue = formatState.IsContinue;
StartIndex = formatState.StartIndex;
StartIndexDefault = formatState.StartIndexDefault;
IsInTable = formatState.IsInTable;
_pb = formatState.HasParaBorder ? new ParaBorder(formatState.ParaBorder) : null;
// Row settings
// For performance reasons, we don't make a full copy of the Row format information. The implication
// of this is that changes to row format data will propagate back up from nested scopes, which is not
// according to the strict semantics. But in practice, all new rows are explicitly cleared with the
// \trowd keyword, which clears this, so this should be fine.
RowFormat = formatState._rowFormat;
// Other
RtfDestination = formatState.RtfDestination;
IsHidden = formatState.IsHidden;
_stateSkip = formatState.UnicodeSkip;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal void SetCharDefaults()
{
_fBold = false;
_fItalic = false;
_fEngrave = false;
_fShadow = false;
_fScaps = false;
_fOutline = false;
_fSub = false;
_fSuper = false;
_superOffset = 0;
_fs = 24; // Default for RTF
_font = -1;
_codePage = -1;
_cf = -1;
_cb = -1;
_dirChar = DirState.DirLTR;
_ul = ULState.ULNone;
_strike = StrikeState.StrikeNone;
_expand = 0;
_fHidden = false;
_lang = -1;
_langFE = -1;
_langCur = -1;
_fontSlot = FontSlot.LOCH;
}
internal void SetParaDefaults()
{
_sb = 0;
_sa = 0;
_fi = 0;
_ri = 0;
_li = 0;
_align = HAlign.AlignDefault;
_ilvl = 0;
_pnlvl = 0;
_itap = 0;
_ils = -1;
_dirPara = DirState.DirLTR;
_cbPara = -1;
_nParaShading = -1;
_cfPara = -1;
_marker = MarkerStyle.MarkerNone;
_fContinue = false;
_nStartIndex = -1;
_nStartIndexDefault = -1;
_sl = 0;
_slMult = false;
_pb = null;
_fInTable = false;
}
internal void SetRowDefaults()
{
// Just toss old info
RowFormat = null;
}
internal bool IsEqual(FormatState formatState)
{
return
// Font Settings
Bold == formatState.Bold
&& Italic == formatState.Italic
&& Engrave == formatState.Engrave
&& Shadow == formatState.Shadow
&& SCaps == formatState.SCaps
&& Outline == formatState.Outline
&& Super == formatState.Super
&& Sub == formatState.Sub
&& SuperOffset == formatState.SuperOffset
&& FontSize == formatState.FontSize
&& Font == formatState.Font
&& CodePage == formatState.CodePage
&& CF == formatState.CF
&& CB == formatState.CB
&& DirChar == formatState.DirChar
&& UL == formatState.UL
&& Strike == formatState.Strike
&& Expand == formatState.Expand
&& Lang == formatState.Lang
&& LangFE == formatState.LangFE
&& LangCur == formatState.LangCur
&& FontSlot == formatState.FontSlot
// Para settings
&& SB == formatState.SB
&& SA == formatState.SA
&& FI == formatState.FI
&& RI == formatState.RI
&& LI == formatState.LI
&& HAlign == formatState.HAlign
&& ILVL == formatState.ILVL
&& ITAP == formatState.ITAP
&& ILS == formatState.ILS
&& DirPara == formatState.DirPara
&& CFPara == formatState.CFPara
&& CBPara == formatState.CBPara
&& ParaShading == formatState.ParaShading
&& Marker == formatState.Marker
&& IsContinue == formatState.IsContinue
&& StartIndex == formatState.StartIndex
&& StartIndexDefault == formatState.StartIndexDefault
&& SL == formatState.SL
&& SLMult == formatState.SLMult
&& IsInTable == formatState.IsInTable
// Don't include para borders in this test
// Row Settings
// Don't include row settings in this test.
// Other
&& RtfDestination == formatState.RtfDestination
&& IsHidden == formatState.IsHidden
&& UnicodeSkip == formatState.UnicodeSkip;
}
static internal FormatState EmptyFormatState
{
get
{
if (_fsEmptyState == null)
{
_fsEmptyState = new FormatState();
_fsEmptyState.FontSize = -1;
}
return _fsEmptyState;
}
}
internal string GetBorderAttributeString(ConverterState converterState)
{
if (HasParaBorder)
{
return ParaBorder.GetBorderAttributeString(converterState);
}
else
{
return string.Empty;
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
// Random Stacked Properties
internal RtfDestination RtfDestination
{
get
{
return _dest;
}
set
{
_dest = value;
}
}
internal bool IsHidden
{
get
{
return _fHidden;
}
set
{
_fHidden = value;
}
}
internal bool IsContentDestination
{
get
{
return _dest == RtfDestination.DestNormal
|| _dest == RtfDestination.DestFieldResult
|| _dest == RtfDestination.DestShapeResult
|| _dest == RtfDestination.DestShape
|| _dest == RtfDestination.DestListText;
}
}
// Character Style
internal bool Bold
{
get
{
return _fBold;
}
set
{
_fBold = value;
}
}
internal bool Italic
{
get
{
return _fItalic;
}
set
{
_fItalic = value;
}
}
internal bool Engrave
{
get
{
return _fEngrave;
}
set
{
_fEngrave = value;
}
}
internal bool Shadow
{
get
{
return _fShadow;
}
set
{
_fShadow = value;
}
}
internal bool SCaps
{
get
{
return _fScaps;
}
set
{
_fScaps = value;
}
}
internal bool Outline
{
get
{
return _fOutline;
}
set
{
_fOutline = value;
}
}
internal bool Sub
{
get
{
return _fSub;
}
set
{
_fSub = value;
}
}
internal bool Super
{
get
{
return _fSuper;
}
set
{
_fSuper = value;
}
}
internal long SuperOffset
{
get
{
return _superOffset;
}
set
{
_superOffset = value;
}
}
internal long FontSize
{
get
{
return _fs;
}
set
{
_fs = value;
}
}
internal long Font
{
get
{
return _font;
}
set
{
_font = value;
}
}
internal int CodePage
{
get
{
return _codePage;
}
set
{
_codePage = value;
}
}
internal long CF
{
get
{
return _cf;
}
set
{
_cf = value;
}
}
internal long CB
{
get
{
return _cb;
}
set
{
_cb = value;
}
}
internal DirState DirChar
{
get
{
return _dirChar;
}
set
{
_dirChar = value;
}
}
internal ULState UL
{
get
{
return _ul;
}
set
{
_ul = value;
}
}
internal StrikeState Strike
{
get
{
return _strike;
}
set
{
_strike = value;
}
}
internal long Expand
{
get
{
return _expand;
}
set
{
_expand = value;
}
}
internal long Lang
{
get
{
return _lang;
}
set
{
_lang = value;
}
}
internal long LangFE
{
get
{
return _langFE;
}
set
{
_langFE = value;
}
}
internal long LangCur
{
get
{
return _langCur;
}
set
{
_langCur = value;
}
}
internal FontSlot FontSlot
{
get
{
return _fontSlot;
}
set
{
_fontSlot = value;
}
}
// Paragraph Style
internal long SB
{
get
{
return _sb;
}
set
{
_sb = value;
}
}
internal long SA
{
get
{
return _sa;
}
set
{
_sa = value;
}
}
internal long FI
{
get
{
return _fi;
}
set
{
_fi = value;
}
}
internal long RI
{
get
{
return _ri;
}
set
{
_ri = value;
}
}
internal long LI
{
get
{
return _li;
}
set
{
_li = value;
}
}
internal HAlign HAlign
{
get
{
return _align;
}
set
{
_align = value;
}
}
internal long ILVL
{
get
{
return _ilvl;
}
set
{
if (value >= 0 && value <= MAX_LIST_DEPTH)
{
_ilvl = value;
}
}
}
internal long PNLVL
{
get
{
return _pnlvl;
}
set
{
_pnlvl = value;
}
}
internal long ITAP
{
get
{
return _itap;
}
set
{
if (value >= 0 && value <= MAX_TABLE_DEPTH)
{
_itap = value;
}
}
}
internal long ILS
{
get
{
return _ils;
}
set
{
_ils = value;
}
}
internal DirState DirPara
{
get
{
return _dirPara;
}
set
{
_dirPara = value;
}
}
internal long CFPara
{
get
{
return _cfPara;
}
set
{
_cfPara = value;
}
}
internal long CBPara
{
get
{
return _cbPara;
}
set
{
_cbPara = value;
}
}
internal long ParaShading
{
get
{
return _nParaShading;
}
set
{
_nParaShading = Validators.MakeValidShading(value);
}
}
internal MarkerStyle Marker
{
get
{
return _marker;
}
set
{
_marker = value;
}
}
internal bool IsContinue
{
get
{
return _fContinue;
}
set
{
_fContinue = value;
}
}
internal long StartIndex
{
get
{
return _nStartIndex;
}
set
{
_nStartIndex = value;
}
}
internal long StartIndexDefault
{
get
{
return _nStartIndexDefault;
}
set
{
_nStartIndexDefault = value;
}
}
internal long SL
{
get
{
return _sl;
}
set
{
_sl = value;
}
}
internal bool SLMult
{
get
{
return _slMult;
}
set
{
_slMult = value;
}
}
internal bool IsInTable
{
get
{
return _fInTable;
}
set
{
_fInTable = value;
}
}
internal long TableLevel
{
get
{
if (_fInTable || _itap > 0)
{
return _itap > 0 ? _itap : 1;
}
else
{
return 0;
}
}
}
internal long ListLevel
{
get
{
if (_ils >= 0 || _ilvl > 0)
{
return _ilvl > 0 ? _ilvl + 1 : 1;
}
else if (PNLVL > 0)
{
return PNLVL;
}
else if (_marker != MarkerStyle.MarkerNone)
{
return 1;
}
else
{
return 0;
}
}
}
internal int UnicodeSkip
{
get
{
return _stateSkip;
}
set
{
if (value >= 0 && value < 0xffff)
{
_stateSkip = value;
}
}
}
internal RowFormat RowFormat
{
get
{
// Allocate on access.
if (_rowFormat == null)
{
_rowFormat = new RowFormat();
}
return _rowFormat;
}
set
{
_rowFormat = value;
}
}
internal bool HasRowFormat
{
get
{
return _rowFormat != null;
}
}
internal ParaBorder ParaBorder
{
get
{
if (_pb == null)
{
_pb = new ParaBorder();
}
return _pb;
}
}
internal bool HasParaBorder
{
get
{
return _pb != null && !_pb.IsNone;
}
}
// Image type property
internal RtfImageFormat ImageFormat
{
get
{
return _imageFormat;
}
set
{
_imageFormat = value;
}
}
// Image source name property
internal string ImageSource
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
}
}
// Image width property
internal double ImageWidth
{
get
{
return _imageWidth;
}
set
{
_imageWidth = value;
}
}
// Image height property
internal double ImageHeight
{
get
{
return _imageHeight;
}
set
{
_imageHeight = value;
}
}
internal double ImageBaselineOffset
{
get
{
return _imageBaselineOffset;
}
set
{
_imageBaselineOffset = value;
}
}
internal bool IncludeImageBaselineOffset
{
get
{
return _isIncludeImageBaselineOffset;
}
set
{
_isIncludeImageBaselineOffset = value;
}
}
// Image width property
internal double ImageScaleWidth
{
get
{
return _imageScaleWidth;
}
set
{
_imageScaleWidth = value;
}
}
// Image height property
internal double ImageScaleHeight
{
get
{
return _imageScaleHeight;
}
set
{
_imageScaleHeight = value;
}
}
// IsImageDataBinary: The default is false that is hex data for image
internal bool IsImageDataBinary
{
get
{
return _isImageDataBinary;
}
set
{
_isImageDataBinary = value;
}
}
// Image stretch property to apply scale factor
internal string ImageStretch
{
get
{
return _imageStretch;
}
set
{
_imageStretch = value;
}
}
// Image stretch direction property to apply scale factor
internal string ImageStretchDirection
{
get
{
return _imageStretchDirection;
}
set
{
_imageStretchDirection = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private RtfDestination _dest;
private bool _fBold;
private bool _fItalic;
private bool _fSuper;
private bool _fSub;
private bool _fOutline;
private bool _fEngrave;
private bool _fShadow;
private bool _fScaps;
private long _fs; // Font size in half points
private long _font; // Index into font table
private int _codePage; // Cache of code page from font
private long _superOffset; // Sub/super offset
private long _cf; // Foreground color index
private long _cb; // Background color index
private DirState _dirChar; // Character level direction
private ULState _ul;
private StrikeState _strike;
private long _expand;
private long _lang;
private long _langFE;
private long _langCur;
private FontSlot _fontSlot;
// Para style flags and values
private long _sa; // Space After
private long _sb; // Space Before
private long _li; // Left Indent
private long _ri; // Right Indent
private long _fi; // First Indent
private HAlign _align; // Paragraph alignment
private long _ils; // List override index
private long _ilvl; // 0-based List level
private long _pnlvl; // 1-based old style List level
private long _itap; // Table level
private DirState _dirPara; // Paragraph level direction
private long _cfPara; // Paragraph fill color
private long _cbPara; // Paragraph pattern background color
private long _nParaShading; // Paragraph shading in 100's of a percent
private MarkerStyle _marker; // Type of bullet for old-style RTF
private bool _fContinue; // Continue list numbering? (without number)
private long _nStartIndex; // List start index
private long _nStartIndexDefault; // List start index default value
private long _sl;
private bool _slMult;
private ParaBorder _pb;
private bool _fInTable; // Paragraph is in table
private bool _fHidden; // Hidden text
private int _stateSkip;
private RowFormat _rowFormat;
private static FormatState _fsEmptyState = null;
// Image property fields
private RtfImageFormat _imageFormat;
private string _imageSource;
private double _imageWidth;
private double _imageHeight;
private double _imageBaselineOffset;
private bool _isIncludeImageBaselineOffset = false;
private double _imageScaleWidth;
private double _imageScaleHeight;
private bool _isImageDataBinary;
private string _imageStretch;
private string _imageStretchDirection;
#endregion Private Fields
//------------------------------------------------------
//
// Private Const
//
//------------------------------------------------------
#region Private Const
private const int MAX_LIST_DEPTH = 32;
private const int MAX_TABLE_DEPTH = 32;
#endregion Private Const
}
internal enum BorderType
{
BorderNone,
BorderSingle,
BorderDouble
// ... lots more
}
internal class BorderFormat
{
internal BorderFormat()
{
SetDefaults();
}
internal BorderFormat(BorderFormat cb)
{
CF = cb.CF;
Width = cb.Width;
Type = cb.Type;
}
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal long CF
{
get
{
return _cf;
}
set
{
_cf = value;
}
}
internal long Width
{
get
{
return _width;
}
set
{
_width = Validators.MakeValidBorderWidth(value);
}
}
internal long EffectiveWidth
{
get
{
switch (Type)
{
case BorderType.BorderNone: return 0;
case BorderType.BorderDouble: return Width * 2;
default:
case BorderType.BorderSingle: return Width;
}
}
}
internal BorderType Type
{
get
{
return _type;
}
set
{
_type = value;
}
}
internal bool IsNone
{
get
{
return EffectiveWidth <= 0 || Type == BorderType.BorderNone;
}
}
internal string RTFEncoding =>
IsNone ? "\\brdrnone" :
CF < 0 ? string.Create(CultureInfo.InvariantCulture, stackalloc char[128], $"\\brdrs\\brdrw{EffectiveWidth}") :
string.Create(CultureInfo.InvariantCulture, stackalloc char[128], $"\\brdrs\\brdrw{EffectiveWidth}\\brdrcf{CF}");
static internal BorderFormat EmptyBorderFormat
{
get
{
if (_emptyBorderFormat == null)
{
_emptyBorderFormat = new BorderFormat();
}
return _emptyBorderFormat;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal void SetDefaults()
{
_cf = -1;
_width = 0;
_type = BorderType.BorderNone;
}
#endregion Internal Methods
private long _cf;
private long _width;
private BorderType _type;
static private BorderFormat _emptyBorderFormat = null;
}
internal class ParaBorder
{
internal ParaBorder()
{
BorderLeft = new BorderFormat();
BorderTop = new BorderFormat();
BorderRight = new BorderFormat();
BorderBottom = new BorderFormat();
BorderAll = new BorderFormat();
Spacing = 0;
}
internal ParaBorder(ParaBorder pb)
{
BorderLeft = new BorderFormat(pb.BorderLeft);
BorderTop = new BorderFormat(pb.BorderTop);
BorderRight = new BorderFormat(pb.BorderRight);
BorderBottom = new BorderFormat(pb.BorderBottom);
BorderAll = new BorderFormat(pb.BorderAll);
Spacing = pb.Spacing;
}
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal BorderFormat BorderLeft
{
get
{
return _bfLeft;
}
set
{
_bfLeft = value;
}
}
internal BorderFormat BorderTop
{
get
{
return _bfTop;
}
set
{
_bfTop = value;
}
}
internal BorderFormat BorderRight
{
get
{
return _bfRight;
}
set
{
_bfRight = value;
}
}
internal BorderFormat BorderBottom
{
get
{
return _bfBottom;
}
set
{
_bfBottom = value;
}
}
internal BorderFormat BorderAll
{
get
{
return _bfAll;
}
set
{
_bfAll = value;
}
}
internal long Spacing
{
get
{
return _nSpacing;
}
set
{
_nSpacing = value;
}
}
internal long CF
{
get
{
return BorderLeft.CF;
}
set
{
BorderLeft.CF = value;
BorderTop.CF = value;
BorderRight.CF = value;
BorderBottom.CF = value;
BorderAll.CF = value;
}
}
internal bool IsNone
{
get
{
return BorderLeft.IsNone && BorderTop.IsNone
&& BorderRight.IsNone && BorderBottom.IsNone
&& BorderAll.IsNone;
}
}
internal string GetBorderAttributeString(ConverterState converterState)
{
if (IsNone)
{
return string.Empty;
}
// Build the border attribute string based on border values
StringBuilder sb = new StringBuilder();
// Left,Top,Right,Bottom
sb.Append(" BorderThickness=\"");
if (!BorderAll.IsNone)
{
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderAll.EffectiveWidth));
}
else
{
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderLeft.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderTop.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderRight.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderBottom.EffectiveWidth));
}
sb.Append('"');
ColorTableEntry entry = null;
if (CF >= 0)
{
entry = converterState.ColorTable.EntryAt((int)CF);
}
if (entry != null)
{
sb.Append(" BorderBrush=\"");
sb.Append(entry.Color.ToString());
sb.Append('"');
}
else
{
sb.Append(" BorderBrush=\"#FF000000\"");
}
if (Spacing != 0)
{
sb.Append(" Padding=\"");
sb.Append(Converters.TwipToPositivePxString(Spacing));
sb.Append('"');
}
return sb.ToString();
}
internal string RTFEncoding
{
get
{
if (IsNone)
{
return "\\brdrnil";
}
StringBuilder sb = new StringBuilder();
sb.Append("\\brdrl");
sb.Append(BorderLeft.RTFEncoding);
if (BorderLeft.CF >= 0)
{
sb.Append("\\brdrcf");
sb.Append(BorderLeft.CF.ToString(CultureInfo.InvariantCulture));
}
sb.Append("\\brdrt");
sb.Append(BorderTop.RTFEncoding);
if (BorderTop.CF >= 0)
{
sb.Append("\\brdrcf");
sb.Append(BorderTop.CF.ToString(CultureInfo.InvariantCulture));
}
sb.Append("\\brdrr");
sb.Append(BorderRight.RTFEncoding);
if (BorderRight.CF >= 0)
{
sb.Append("\\brdrcf");
sb.Append(BorderRight.CF.ToString(CultureInfo.InvariantCulture));
}
sb.Append("\\brdrb");
sb.Append(BorderBottom.RTFEncoding);
if (BorderBottom.CF >= 0)
{
sb.Append("\\brdrcf");
sb.Append(BorderBottom.CF.ToString(CultureInfo.InvariantCulture));
}
sb.Append("\\brsp");
sb.Append(Spacing.ToString(CultureInfo.InvariantCulture));
return sb.ToString();
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private BorderFormat _bfLeft;
private BorderFormat _bfTop;
private BorderFormat _bfRight;
private BorderFormat _bfBottom;
private BorderFormat _bfAll;
private long _nSpacing;
#endregion Private Fields
}
internal enum WidthType
{
WidthIgnore = 0,
WidthAuto = 1,
WidthPercent = 2, // Actually 50ths
WidthTwips = 3
}
internal class CellWidth
{
internal CellWidth()
{
Type = WidthType.WidthAuto;
Value = 0;
}
internal CellWidth(CellWidth cw)
{
Type = cw.Type;
Value = cw.Value;
}
internal WidthType Type
{
get
{
return _type;
}
set
{
_type = value;
}
}
internal long Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
internal void SetDefaults()
{
Type = WidthType.WidthAuto;
Value = 0;
}
private WidthType _type;
private long _value;
}
internal class CellFormat
{
internal CellFormat()
{
BorderLeft = new BorderFormat();
BorderRight = new BorderFormat();
BorderBottom = new BorderFormat();
BorderTop = new BorderFormat();
Width = new CellWidth();
SetDefaults();
IsPending = true;
}
internal CellFormat(CellFormat cf)
{
CellX = cf.CellX;
IsCellXSet = cf.IsCellXSet;
Width = new CellWidth(cf.Width);
CB = cf.CB;
CF = cf.CF;
Shading = cf.Shading;
PaddingTop = cf.PaddingTop;
PaddingBottom = cf.PaddingBottom;
PaddingRight = cf.PaddingRight;
PaddingLeft = cf.PaddingLeft;
BorderLeft = new BorderFormat(cf.BorderLeft);
BorderRight = new BorderFormat(cf.BorderRight);
BorderBottom = new BorderFormat(cf.BorderBottom);
BorderTop = new BorderFormat(cf.BorderTop);
SpacingTop = cf.SpacingTop;
SpacingBottom = cf.SpacingBottom;
SpacingRight = cf.SpacingRight;
SpacingLeft = cf.SpacingLeft;
VAlign = VAlign.AlignTop;
IsPending = true;
IsHMerge = cf.IsHMerge;
IsHMergeFirst = cf.IsHMergeFirst;
IsVMerge = cf.IsVMerge;
IsVMergeFirst = cf.IsVMergeFirst;
}
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal long CB
{
get
{
return _cb;
}
set
{
_cb = value;
}
}
internal long CF
{
get
{
return _cf;
}
set
{
_cf = value;
}
}
internal long Shading
{
get
{
return _nShading;
}
set
{
_nShading = Validators.MakeValidShading(value);
}
}
internal long PaddingLeft
{
get
{
return _padL;
}
set
{
_padL = value;
}
}
internal long PaddingRight
{
get
{
return _padR;
}
set
{
_padR = value;
}
}
internal long PaddingTop
{
get
{
return _padT;
}
set
{
_padT = value;
}
}
internal long PaddingBottom
{
get
{
return _padB;
}
set
{
_padB = value;
}
}
internal BorderFormat BorderTop
{
get
{
return _brdT;
}
set
{
_brdT = value;
}
}
internal BorderFormat BorderBottom
{
get
{
return _brdB;
}
set
{
_brdB = value;
}
}
internal BorderFormat BorderLeft
{
get
{
return _brdL;
}
set
{
_brdL = value;
}
}
internal BorderFormat BorderRight
{
get
{
return _brdR;
}
set
{
_brdR = value;
}
}
internal CellWidth Width
{
get
{
return _width;
}
set
{
_width = value;
}
}
internal long CellX
{
get
{
return _nCellX;
}
set
{
_nCellX = value;
_fCellXSet = true;
}
}
internal bool IsCellXSet
{
get
{
return _fCellXSet;
}
set
{
_fCellXSet = value;
}
}
internal VAlign VAlign
{
set
{
_valign = value;
}
}
internal long SpacingTop
{
get
{
return _spaceT;
}
set
{
_spaceT = value;
}
}
internal long SpacingLeft
{
get
{
return _spaceL;
}
set
{
_spaceL = value;
}
}
internal long SpacingBottom
{
get
{
return _spaceB;
}
set
{
_spaceB = value;
}
}
internal long SpacingRight
{
get
{
return _spaceR;
}
set
{
_spaceR = value;
}
}
internal bool IsPending
{
get
{
return _fPending;
}
set
{
_fPending = value;
}
}
internal bool IsHMerge
{
get
{
return _fHMerge;
}
set
{
_fHMerge = value;
}
}
internal bool IsHMergeFirst
{
get
{
return _fHMergeFirst;
}
set
{
_fHMergeFirst = value;
}
}
internal bool IsVMerge
{
get
{
return _fVMerge;
}
set
{
_fVMerge = value;
}
}
internal bool IsVMergeFirst
{
get
{
return _fVMergeFirst;
}
set
{
_fVMergeFirst = value;
}
}
internal bool HasBorder
{
get
{
return BorderLeft.EffectiveWidth > 0
|| BorderRight.EffectiveWidth > 0
|| BorderTop.EffectiveWidth > 0
|| BorderBottom.EffectiveWidth > 0;
}
}
internal string RTFEncodingForWidth
{
get
{
StringBuilder sb = new StringBuilder();
sb.Append("\\clftsWidth");
int t = (int)Width.Type;
sb.Append(t.ToString(CultureInfo.InvariantCulture));
sb.Append("\\clwWidth");
sb.Append(Width.Value.ToString(CultureInfo.InvariantCulture));
sb.Append("\\cellx");
sb.Append(CellX.ToString(CultureInfo.InvariantCulture));
return sb.ToString();
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal void SetDefaults()
{
CellX = -1;
IsCellXSet = false;
Width.SetDefaults();
CB = -1;
CF = -1;
Shading = -1;
PaddingTop = 0;
PaddingBottom = 0;
PaddingRight = 0;
PaddingLeft = 0;
BorderLeft.SetDefaults();
BorderRight.SetDefaults();
BorderBottom.SetDefaults();
BorderTop.SetDefaults();
SpacingTop = 0;
SpacingBottom = 0;
SpacingRight = 0;
SpacingLeft = 0;
VAlign = VAlign.AlignTop;
IsHMerge = false;
IsHMergeFirst = false;
IsVMerge = false;
IsVMergeFirst = false;
}
internal string GetBorderAttributeString(ConverterState converterState)
{
Debug.Assert(HasBorder);
// Build the border attribute string based on border values
StringBuilder sb = new StringBuilder();
// Left,Top,Right,Bottom
sb.Append(" BorderThickness=\"");
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderLeft.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderTop.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderRight.EffectiveWidth));
sb.Append(',');
sb.Append(Converters.TwipToPositiveVisiblePxString(BorderBottom.EffectiveWidth));
sb.Append('"');
// Only grab one color
ColorTableEntry entry = null;
if (BorderLeft.CF >= 0)
{
entry = converterState.ColorTable.EntryAt((int)BorderLeft.CF);
}
if (entry != null)
{
sb.Append(" BorderBrush=\"");
sb.Append(entry.Color.ToString(CultureInfo.InvariantCulture));
sb.Append('"');
}
else
{
sb.Append(" BorderBrush=\"#FF000000\"");
}
return sb.ToString();
}
internal string GetPaddingAttributeString() =>
// Build the padding attribute string based on padding values
$" Padding=\"{Converters.TwipToPositivePxString(PaddingLeft)},{Converters.TwipToPositivePxString(PaddingTop)},{Converters.TwipToPositivePxString(PaddingRight)},{Converters.TwipToPositivePxString(PaddingBottom)}\"";
#endregion Internal Methods
private long _cb;
private long _cf;
private long _nShading;
private long _padT;
private long _padB;
private long _padR;
private long _padL;
private long _spaceT;
private long _spaceB;
private long _spaceR;
private long _spaceL;
private long _nCellX;
private CellWidth _width;
private VAlign _valign;
private BorderFormat _brdL;
private BorderFormat _brdR;
private BorderFormat _brdT;
private BorderFormat _brdB;
private bool _fPending;
private bool _fHMerge;
private bool _fHMergeFirst;
private bool _fVMerge;
private bool _fVMergeFirst;
private bool _fCellXSet;
}
internal class RowFormat
{
internal RowFormat()
{
_rowCellFormat = new CellFormat();
_widthA = new CellWidth();
_widthB = new CellWidth();
_widthRow = new CellWidth();
_cellFormats = new ArrayList();
_dir = DirState.DirLTR;
_nTrgaph = -1;
_nTrleft = 0;
}
internal RowFormat(RowFormat ri)
{
_rowCellFormat = new CellFormat(ri.RowCellFormat);
_cellFormats = new ArrayList();
_widthA = new CellWidth(ri.WidthA);
_widthB = new CellWidth(ri.WidthB);
_widthRow = new CellWidth(ri.WidthRow);
_nTrgaph = ri.Trgaph;
_dir = ri.Dir;
_nTrleft = ri._nTrleft;
for (int i = 0; i < ri.CellCount; i++)
{
_cellFormats.Add(new CellFormat(ri.NthCellFormat(i)));
}
}
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal CellFormat RowCellFormat
{
get
{
return _rowCellFormat;
}
}
internal int CellCount
{
get
{
return _cellFormats.Count;
}
}
internal CellFormat TopCellFormat
{
get
{
return CellCount > 0 ? NthCellFormat(CellCount - 1) : null;
}
}
internal CellWidth WidthA
{
get
{
return _widthA;
}
}
internal CellWidth WidthB
{
get
{
return _widthB;
}
}
internal CellWidth WidthRow
{
get
{
return _widthRow;
}
}
internal long Trgaph
{
get
{
return _nTrgaph;
}
set
{
_nTrgaph = value;
}
}
internal long Trleft
{
get
{
return _nTrleft;
}
set
{
_nTrleft = value;
}
}
internal DirState Dir
{
get
{
return _dir;
}
set
{
_dir = value;
}
}
internal bool IsVMerge
{
get
{
for (int i = 0; i < CellCount; i++)
{
if (NthCellFormat(i).IsVMerge)
{
return true;
}
}
return false;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal CellFormat NthCellFormat(int n)
{
// If asked for a cell format beyond the bounds specified, just use row defaults.
// This probably indicates malformed content, but makes reading more robust.
if (n < 0 || n >= CellCount)
{
return RowCellFormat;
}
return (CellFormat)_cellFormats[n];
}
internal CellFormat NextCellFormat()
{
_cellFormats.Add(new CellFormat(RowCellFormat));
return TopCellFormat;
}
internal CellFormat CurrentCellFormat()
{
if (CellCount == 0 || !TopCellFormat.IsPending)
{
return NextCellFormat();
}
else
{
return TopCellFormat;
}
}
internal void CanonicalizeWidthsFromRTF()
{
if (CellCount == 0)
{
return;
}
CellFormat cfPrev = null;
long cellx = Trleft;
// Make sure widths and cellx are set up. For merged cells, the first cell in the merged set has
// the real width while the last cell in the merged set has the real CellX. I put the real CellX
// in the first merged cell so I can just use that one.
for (int i = 0; i < CellCount; i++)
{
CellFormat cf = NthCellFormat(i);
// Ignore HMerge Cells
if (cf.IsHMerge)
{
continue;
}
// Grab CellX from last cell in range of merged cells
if (cf.IsHMergeFirst)
{
for (int k = i + 1; k < CellCount; k++)
{
CellFormat cf1 = NthCellFormat(k);
if (cf1.IsHMerge)
{
cf.CellX = cf1.CellX;
}
else
{
break;
}
}
}
if (cf.Width.Value == 0 && cf.IsCellXSet)
{
cf.Width.Type = WidthType.WidthTwips;
cf.Width.Value = (cfPrev == null) ? cf.CellX - Trleft : cf.CellX - cfPrev.CellX;
}
else if (cf.Width.Value > 0 && !cf.IsCellXSet)
{
cellx += cf.Width.Value;
cf.CellX = cellx;
}
cfPrev = cf;
}
// It's also important that CellX be monotonic.
cellx = NthCellFormat(0).CellX;
for (int i = 1; i < CellCount; i++)
{
CellFormat cf = NthCellFormat(i);
if (cf.CellX < cellx)
{
cf.CellX = cellx + 1;
}
cellx = cf.CellX;
}
}
internal void CanonicalizeWidthsFromXaml()
{
long nCellX = Trleft;
for (int i = 0; i < CellCount; i++)
{
CellFormat cf = NthCellFormat(i);
if (cf.Width.Type == WidthType.WidthTwips)
{
nCellX += cf.Width.Value;
}
else
{
nCellX += 1440; // arbitrary - one inch.
}
cf.CellX = nCellX;
}
}
#endregion Internal Methods
private CellFormat _rowCellFormat;
private CellWidth _widthA;
private CellWidth _widthB;
private CellWidth _widthRow;
private ArrayList _cellFormats;
private long _nTrgaph;
private long _nTrleft;
private DirState _dir;
}
internal class MarkerListEntry
{
internal MarkerListEntry()
{
_marker = MarkerStyle.MarkerBullet;
_nILS = -1;
_nStartIndexOverride = -1;
_nStartIndexDefault = -1;
_nVirtualListLevel = -1;
}
internal MarkerStyle Marker
{
get
{
return _marker;
}
set
{
_marker = value;
}
}
internal long StartIndexOverride
{
get
{
return _nStartIndexOverride;
}
set
{
_nStartIndexOverride = value;
}
}
internal long StartIndexDefault
{
get
{
return _nStartIndexDefault;
}
set
{
_nStartIndexDefault = value;
}
}
internal long VirtualListLevel
{
get
{
return _nVirtualListLevel;
}
set
{
_nVirtualListLevel = value;
}
}
internal long StartIndexToUse
{
get
{
return _nStartIndexOverride > 0 ? _nStartIndexOverride : _nStartIndexDefault;
}
}
internal long ILS
{
get
{
return _nILS;
}
set
{
_nILS = value;
}
}
private MarkerStyle _marker;
private long _nStartIndexOverride;
private long _nStartIndexDefault;
private long _nVirtualListLevel;
private long _nILS;
}
internal class MarkerList : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal MarkerList()
: base(5)
{
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal MarkerListEntry EntryAt(int index)
{
return (MarkerListEntry)this[index];
}
internal void AddEntry(MarkerStyle m, long nILS, long nStartIndexOverride, long nStartIndexDefault, long nLevel)
{
MarkerListEntry entry = new MarkerListEntry();
entry.Marker = m;
entry.StartIndexOverride = nStartIndexOverride;
entry.StartIndexDefault = nStartIndexDefault;
entry.VirtualListLevel = nLevel;
entry.ILS = nILS;
Add(entry);
}
#endregion Internal Methods
}
/// <summary>
/// FontTableEntry
/// </summary>
internal class FontTableEntry
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal FontTableEntry()
{
_index = -1;
_codePage = -1;
_charSet = 0;
_bNameSealed = false;
_bPending = true;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal int Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
internal string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
internal bool IsNameSealed
{
get
{
return _bNameSealed;
}
set
{
_bNameSealed = value;
}
}
internal bool IsPending
{
get
{
return _bPending;
}
set
{
_bPending = value;
}
}
internal int CodePage
{
get
{
return _codePage;
}
set
{
_codePage = value;
}
}
internal int CodePageFromCharSet
{
set
{
int cp = CharSetToCodePage(value);
if (cp != 0)
{
CodePage = cp;
}
}
}
internal int CharSet
{
get
{
return _charSet;
}
set
{
_charSet = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal static int CharSetToCodePage(int cs)
{
switch (cs)
{
case 0: // ANSI
return 1252; // ANSI means use 1252
case 1: // DEFAULT
return -1; // -1 means use default code page
case 2: // Symbol
return 1252; // Symbol isn't a charset
case 3: // Invalid
return -1; // -1 means use ansicpg
case 77: // Mac
return 10000;
case 78: // Shift JIS - bug in MacWord98J
case 128: // Shift JIS
return 932;
case 129: // Hangul
return 949;
case 130: // Johab
return 1361;
case 134: // GB2312
return 936;
case 136: // Big5
return 950;
case 161: // Greek
return 1253;
case 162: // Turkish
return 1254;
case 163: // Vietnamese
return 1258;
case 177: // Hebrew
return 1255;
case 178: // Arabic
return 1256;
case 179: // Arabic Traditional
return 1256;
case 180: // Arabic user
return 1256;
case 181: // Hebrew user
return 1255;
case 186: // Baltic
return 1257;
case 204: // Russian
return 1251;
case 222: // Thai
return 874;
case 238: // Eastern European
return 1250;
case 254: // PC 437
return 437;
case 255: // OEM
return 850;
default:
return 0;
}
}
internal void ComputePreferredCodePage()
{
int[] CodePageList = {
1252, 932, 949, 1361, 936, 950, 1253, 1254, 1258, 1255, 1256, 1257, 1251, 874, 1250, 437, 850 };
CodePage = 1252;
CharSet = 0;
if (Name != null && Name.Length > 0)
{
byte[] rgBytes = new byte[Name.Length * 6];
char[] rgChars = new char[Name.Length * 6];
for (int i = 0; i < CodePageList.Length; i++)
{
Encoding e = InternalEncoding.GetEncoding(CodePageList[i]);
int cb = e.GetBytes(Name, 0, Name.Length, rgBytes, 0);
int cch = e.GetChars(rgBytes, 0, cb, rgChars, 0);
if (cch == Name.Length)
{
int k = 0;
for (k = 0; k < cch; k++)
{
if (rgChars[k] != Name[k])
{
break;
}
}
// This code page can encode this font name.
if (k == cch)
{
CodePage = CodePageList[i];
CharSet = CodePageToCharSet(CodePage);
break;
}
}
}
// Set the symbol charset for symbol font
if (IsSymbolFont(Name))
{
CharSet = 2 /* Symbol Charset */;
}
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private static int CodePageToCharSet(int cp)
{
switch (cp)
{
case 1252: // ANSI
return 0; // ANSI means use 1252
case 10000: // Mac
return 77;
case 932: // Shift JIS
return 128;
case 949: // Hangul
return 129;
case 1361: // Johab
return 130;
case 936: // GB2312
return 134;
case 950: // Big5
return 136;
case 1253: // Greek
return 161;
case 1254: // Turkish
return 162;
case 1258: // Vietnamese
return 163;
case 1255: // Hebrew
return 177;
case 1256: // Arabic
return 178;
case 1257: // Baltic
return 186;
case 1251: // Russian
return 204;
case 874: // Thai
return 222;
case 1250: // Eastern European
return 238;
case 437: // PC 437
return 254;
case 850: // OEM
return 255;
default:
return 0;
}
}
/// <summary>
/// Return true if the specified typeface name is the symbol font.
/// </summary>
private static bool IsSymbolFont(string typefaceName)
{
bool isSymbolFont = false;
Typeface typeface = new Typeface(typefaceName);
if (typeface != null)
{
GlyphTypeface glyphTypeface = typeface.TryGetGlyphTypeface();
if (glyphTypeface != null && glyphTypeface.Symbol)
{
isSymbolFont = true;
}
}
return isSymbolFont;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private string _name;
private int _index;
private int _codePage;
private int _charSet;
private bool _bNameSealed;
private bool _bPending;
#endregion Private Fields
}
/// <summary>
/// FontTable that includes the font table.
/// </summary>
internal class FontTable : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal FontTable()
: base(20)
{
_fontMappings = null;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal FontTableEntry DefineEntry(int index)
{
// Might happen with bad input
FontTableEntry entry = FindEntryByIndex(index);
if (entry != null)
{
// Re-open it
entry.IsPending = true;
entry.Name = null;
return entry;
}
entry = new FontTableEntry();
entry.Index = index;
Add(entry);
return entry;
}
internal FontTableEntry FindEntryByIndex(int index)
{
for (int i = 0; i < Count; i++)
{
FontTableEntry entry = EntryAt(i);
if (entry.Index == index)
{
return entry;
}
}
return null;
}
internal FontTableEntry FindEntryByName(string name)
{
for (int i = 0; i < Count; i++)
{
FontTableEntry entry = EntryAt(i);
if (name.Equals(entry.Name))
{
return entry;
}
}
return null;
}
internal FontTableEntry EntryAt(int index)
{
return (FontTableEntry)this[index];
}
internal int DefineEntryByName(string name)
{
int maxIndex = -1;
for (int i = 0; i < Count; i++)
{
FontTableEntry entry = EntryAt(i);
if (name.Equals(entry.Name))
{
return entry.Index;
}
if (entry.Index > maxIndex)
{
maxIndex = entry.Index;
}
}
// Not there - define one.
FontTableEntry newEntry = new FontTableEntry();
newEntry.Index = maxIndex + 1;
Add(newEntry);
newEntry.Name = name;
return maxIndex + 1;
}
internal void MapFonts()
{
Hashtable map = FontMappings;
for (int i = 0; i < Count; i++)
{
FontTableEntry entry = EntryAt(i);
if (entry.Name != null)
{
string mappedName = (string)map[entry.Name.ToLower(CultureInfo.InvariantCulture)];
if (mappedName != null)
{
entry.Name = mappedName;
}
else
{
int iCP = entry.Name.IndexOf('(');
if (iCP >= 0)
{
while (iCP > 0 && entry.Name[iCP - 1] == ' ')
iCP--;
entry.Name = entry.Name.Substring(0, iCP);
}
}
}
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal FontTableEntry CurrentEntry
{
get
{
if (Count == 0)
{
return null;
}
// Find Pending Entry
for (int i = Count - 1; i >= 0; i--)
{
FontTableEntry entry = EntryAt(i);
if (entry.IsPending)
{
return entry;
}
}
return EntryAt(Count - 1);
}
}
internal Hashtable FontMappings
{
get
{
if (_fontMappings == null)
{
_fontMappings = new Hashtable();
RegistryKey rk = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes");
if (rk != null)
{
string[] names = rk.GetValueNames();
foreach (string name in names)
{
string value = (string)rk.GetValue(name);
if (name.Length > 0 && value.Length > 0)
{
string lhs_name = name;
string lhs_tag = string.Empty;
string rhs_name = value;
string rhs_tag = string.Empty;
int i;
i = name.IndexOf(',');
if (i >= 0)
{
lhs_name = name.Substring(0, i);
lhs_tag = name.Substring(i + 1, name.Length - i - 1);
}
i = value.IndexOf(',');
if (i >= 0)
{
rhs_name = value.Substring(0, i);
rhs_tag = value.Substring(i + 1, value.Length - i - 1);
}
if (lhs_name.Length > 0 && rhs_name.Length > 0)
{
bool bAdd = false;
// If both entries specify charset, they must match.
if (lhs_tag.Length > 0 && rhs_tag.Length > 0)
{
if (string.Equals(lhs_tag, rhs_tag, StringComparison.OrdinalIgnoreCase))
{
bAdd = true;
}
}
// If neither specifies a charset, the tagged (left) entry must be a substring.
else if (lhs_tag.Length == 0 && rhs_tag.Length == 0)
{
if (lhs_name.Length > rhs_name.Length)
{
ReadOnlySpan<char> s = lhs_name.AsSpan(0, rhs_name.Length);
if (s.Equals(rhs_name, StringComparison.OrdinalIgnoreCase))
{
bAdd = true;
}
}
}
// If just the name specifies the charset, use it.
else if (lhs_tag.Length > 0 && rhs_tag.Length == 0)
{
bAdd = true;
}
// OK, actually add the mapping.
if (bAdd)
{
// Don't add a new mapping if one exists
string keyname = lhs_name.ToLower(CultureInfo.InvariantCulture);
if (_fontMappings[keyname] == null)
{
_fontMappings.Add(keyname, rhs_name);
}
}
}
}
}
}
}
return _fontMappings;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
Hashtable _fontMappings;
#endregion Private Fields
}
/// <summary>
/// ColorTableEntry that includes color.
/// </summary>
internal class ColorTableEntry
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ColorTableEntry()
{
_color = Color.FromArgb(0xff, 0, 0, 0);
_bAuto = false;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal Color Color
{
get
{
return _color;
}
set
{
_color = value;
}
}
internal bool IsAuto
{
get
{
return _bAuto;
}
set
{
_bAuto = value;
}
}
internal byte Red
{
set
{
_color = Color.FromArgb(0xff, value, _color.G, _color.B);
}
}
internal byte Green
{
set
{
_color = Color.FromArgb(0xff, _color.R, value, _color.B);
}
}
internal byte Blue
{
set
{
_color = Color.FromArgb(0xff, _color.R, _color.G, value);
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private Color _color;
private bool _bAuto;
#endregion Private Fields
}
/// <summary>
/// ColorTableEntry that includes color table.
/// </summary>
internal class ColorTable : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ColorTable()
: base(20)
{
_inProgress = false;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal Color ColorAt(int index)
{
if (index >= 0 && index < Count)
{
return EntryAt(index).Color;
}
else
{
return Color.FromArgb(0xff, 0, 0, 0);
}
}
internal void FinishColor()
{
if (_inProgress)
{
_inProgress = false;
}
else
{
int i = AddColor(Color.FromArgb(0xff, 0, 0, 0));
// Initial unspecified color value is treated as "auto".
EntryAt(i).IsAuto = true;
}
}
internal int AddColor(Color color)
{
// First return existing one
for (int i = 0; i < Count; i++)
{
if (ColorAt(i) == color)
{
return i;
}
}
// OK, need to add one
ColorTableEntry entry = new ColorTableEntry();
entry.Color = color;
Add(entry);
return Count - 1;
}
internal ColorTableEntry EntryAt(int index)
{
if (index >= 0 && index < Count)
{
return (ColorTableEntry)this[index];
}
else
{
return null;
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal byte NewRed
{
set
{
ColorTableEntry entry = GetInProgressEntry();
if (entry != null)
{
entry.Red = value;
}
}
}
internal byte NewGreen
{
set
{
ColorTableEntry entry = GetInProgressEntry();
if (entry != null)
{
entry.Green = value;
}
}
}
internal byte NewBlue
{
set
{
ColorTableEntry entry = GetInProgressEntry();
if (entry != null)
{
entry.Blue = value;
}
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private ColorTableEntry GetInProgressEntry()
{
if (_inProgress)
{
return EntryAt(Count - 1);
}
else
{
_inProgress = true;
ColorTableEntry entry = new ColorTableEntry();
Add(entry);
return entry;
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private bool _inProgress;
#endregion Private Fields
}
/// <summary>
/// ListLevel
/// </summary>
internal class ListLevel
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListLevel()
{
_nStartIndex = 1;
_numberType = MarkerStyle.MarkerArabic;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal long StartIndex
{
get
{
return _nStartIndex;
}
set
{
_nStartIndex = value;
}
}
internal MarkerStyle Marker
{
get
{
return _numberType;
}
set
{
_numberType = value;
}
}
internal FormatState FormatState
{
set
{
_formatState = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private long _nStartIndex;
private MarkerStyle _numberType;
private FormatState _formatState;
#endregion Private Fields
}
/// <summary>
/// ListLevelTable
/// </summary>
internal class ListLevelTable : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListLevelTable()
: base(1)
{
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal ListLevel EntryAt(int index)
{
// Note - we silently handle out of range index values here since the lookup
// might have been based on the structure of the file or the content of some
// keyword parameter.
if (index > Count)
{
index = Count - 1;
}
return (ListLevel)(Count > index && index >= 0 ? this[index] : null);
}
internal ListLevel AddEntry()
{
ListLevel entry = new ListLevel();
Add(entry);
return entry;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal ListLevel CurrentEntry
{
get
{
return Count > 0 ? EntryAt(Count - 1) : null;
}
}
#endregion Internal Properties
}
/// <summary>
/// ListTableEntry
/// </summary>
internal class ListTableEntry
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListTableEntry()
{
_id = 0;
_templateID = 0;
_levels = new ListLevelTable();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal long ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
internal long TemplateID
{
set
{
_templateID = value;
}
}
internal bool Simple
{
set
{
_simple = value;
}
}
internal ListLevelTable Levels
{
get
{
return _levels;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private long _id;
private long _templateID;
private bool _simple;
private ListLevelTable _levels;
#endregion Private Fields
}
/// <summary>
/// ListTable
/// </summary>
internal class ListTable : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListTable()
: base(20)
{
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal ListTableEntry EntryAt(int index)
{
return (ListTableEntry)this[index];
}
internal ListTableEntry FindEntry(long id)
{
for (int i = 0; i < Count; i++)
{
ListTableEntry entry = EntryAt(i);
if (entry.ID == id)
{
return entry;
}
}
return null;
}
internal ListTableEntry AddEntry()
{
ListTableEntry entry = new ListTableEntry();
Add(entry);
return entry;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal ListTableEntry CurrentEntry
{
get
{
return Count > 0 ? EntryAt(Count - 1) : null;
}
}
#endregion Internal Properties
}
/// <summary>
/// ListOverride
/// </summary>
internal class ListOverride
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListOverride()
{
_id = 0;
_index = 0;
_levels = null;
_nStartIndex = -1;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal long ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
internal long Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
internal ListLevelTable Levels
{
get
{
return _levels;
}
set
{
_levels = value;
}
}
internal long StartIndex
{
get
{
return _nStartIndex;
}
set
{
_nStartIndex = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private long _id;
private long _index;
private long _nStartIndex;
private ListLevelTable _levels;
#endregion Private Fields
}
/// <summary>
/// ListOverrideTable
/// </summary>
internal class ListOverrideTable : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal ListOverrideTable()
: base(20)
{
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal ListOverride EntryAt(int index)
{
return (ListOverride)this[index];
}
internal ListOverride FindEntry(int index)
{
for (int i = 0; i < Count; i++)
{
ListOverride entry = EntryAt(i);
if (entry.Index == index)
{
return entry;
}
}
return null;
}
internal ListOverride AddEntry()
{
ListOverride entry = new ListOverride();
Add(entry);
return entry;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal ListOverride CurrentEntry
{
get
{
return Count > 0 ? EntryAt(Count - 1) : null;
}
}
#endregion Internal Properties
}
/// <summary>
/// DocumentNode
/// </summary>
internal class DocumentNode
{
//------------------------------------------------------
//
// Consts
//
//------------------------------------------------------
#region Consts
internal static string[] HtmlNames = new string[]
{
"",
"",
"span",
"br",
"a",
"p",
"ul",
"li",
"table",
"tbody",
"tr",
"td"
};
internal static ReadOnlySpan<int> HtmlLengths => [
0, // unknown
0, // text
4, // span
2, // br
1, // a
1, // p
2, // ul
2, // li
5, // table
6, // tbody
2, // tr
2 // td
];
internal static string[] XamlNames = new string[]
{
"",
"",
"Span",
"LineBreak",
"Hyperlink",
"Paragraph",
"InlineUIContainer",
"BlockUIContainer",
"Image",
"List",
"ListItem",
"Table",
"TableRowGroup",
"TableRow",
"TableCell",
"Section",
"Figure",
"Floater",
"Field",
"ListText"
};
#endregion Consts
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal DocumentNode(DocumentNodeType documentNodeType)
{
_type = documentNodeType;
_bPending = true;
_childCount = 0;
_index = -1;
_dna = null;
_parent = null;
_bTerminated = false;
_bMatched = false;
_bHasMarkerContent = false;
_sCustom = null;
_nRowSpan = 1;
_nColSpan = 1;
_nVirtualListLevel = -1;
_csa = null;
_formatState = new FormatState();
_contentBuilder = new StringBuilder();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal void InheritFormatState(FormatState formatState)
{
_formatState = new FormatState(formatState);
// Reset non-inherited properties
_formatState.LI = 0;
_formatState.RI = 0;
_formatState.SB = 0;
_formatState.SA = 0;
_formatState.FI = 0;
_formatState.Marker = MarkerStyle.MarkerNone;
_formatState.CBPara = -1;
}
internal string GetTagName()
{
return XamlNames[(int)Type];
}
internal DocumentNode GetParentOfType(DocumentNodeType parentType)
{
DocumentNode dn = Parent;
while (dn != null && dn.Type != parentType)
{
dn = dn.Parent;
}
return dn;
}
internal int GetTableDepth()
{
DocumentNode dn = Parent;
int nDepth = 0;
while (dn != null)
{
if (dn.Type == DocumentNodeType.dnTable)
{
nDepth++;
}
dn = dn.Parent;
}
return nDepth;
}
internal int GetListDepth()
{
DocumentNode dn = Parent;
int nDepth = 0;
while (dn != null)
{
if (dn.Type == DocumentNodeType.dnList)
{
nDepth++;
}
else if (dn.Type == DocumentNodeType.dnCell)
{
break;
}
dn = dn.Parent;
}
return nDepth;
}
internal void Terminate(ConverterState converterState)
{
if (!IsTerminated)
{
string plaintext = StripInvalidChars(Xaml);
AppendXamlPrefix(converterState);
Xaml += plaintext;
AppendXamlPostfix(converterState);
IsTerminated = true;
}
}
internal void ConstrainFontPropagation(FormatState fsOrig)
{
// We only output certain font properties at the paragraph level. Ensure the paragraph's formatstate
// only records those properties that are actually written there, so that inline nodes properly
// generate the result.
FormatState.SetCharDefaults();
FormatState.Font = fsOrig.Font;
FormatState.FontSize = fsOrig.FontSize;
FormatState.Bold = fsOrig.Bold;
FormatState.Italic = fsOrig.Italic;
// No, lang can't be written at paragraph level since I have no way of turning it "off", e.g. if
// \lang specified for \par but not for inline text.
// FormatState.LangCur = fsOrig.LangCur;
// No, font color can't be written at paragraph level since I have no way of turning it "off" once
// I've turned it on, so "automatic" color values can't be encoded. I'll just have to skip it here.
// FormatState.CF = fsOrig.CF;
// No, text decorations can't be written at paragraph level since they don't propagate.
// FormatState.UL = fsOrig.UL;
// FormatState.Strike = fsOrig.Strike;
}
internal bool RequiresXamlFontProperties()
{
FormatState fsThis = FormatState;
FormatState fsParent = ParentFormatStateForFont;
return (fsThis.Strike != fsParent.Strike)
|| (fsThis.UL != fsParent.UL)
|| (fsThis.Font != fsParent.Font && fsThis.Font >= 0)
|| (fsThis.FontSize != fsParent.FontSize && fsThis.FontSize >= 0)
|| (fsThis.CF != fsParent.CF)
|| (fsThis.Bold != fsParent.Bold)
|| (fsThis.Italic != fsParent.Italic)
|| (fsThis.LangCur != fsParent.LangCur);
}
internal void AppendXamlFontProperties(ConverterState converterState, StringBuilder sb)
{
FormatState fsThis = FormatState;
FormatState fsParent = ParentFormatStateForFont;
bool bStrike = fsThis.Strike != fsParent.Strike;
bool bUL = fsThis.UL != fsParent.UL;
if (bStrike || bUL)
{
sb.Append(" TextDecorations=\"");
if (bUL)
{
sb.Append("Underline");
}
if (bUL && bStrike)
{
sb.Append(", ");
}
if (bStrike)
{
sb.Append("Strikethrough");
}
sb.Append('"');
}
if (fsThis.Font != fsParent.Font && fsThis.Font >= 0)
{
FontTableEntry entry = converterState.FontTable.FindEntryByIndex((int)fsThis.Font);
if (entry != null && entry.Name != null && !(entry.Name.Equals(string.Empty)))
{
sb.Append(" FontFamily=\"");
// FontFamily should be limited with LF_FACESIZE(32) characters,
// because GDI doesn't support fonts that have the name with more than
// LF_FACESIZE characters.
if (entry.Name.Length > 32)
{
sb.Append(entry.Name, 0, 32);
}
else
{
sb.Append(entry.Name);
}
sb.Append('"');
}
}
if (fsThis.FontSize != fsParent.FontSize && fsThis.FontSize >= 0)
{
sb.Append(" FontSize=\"");
double fs = (double)fsThis.FontSize;
if (fs <= 1f)
{
fs = 2f;
}
sb.Append((fs / 2).ToString(CultureInfo.InvariantCulture));
sb.Append("pt\"");
}
if (fsThis.Bold != fsParent.Bold)
{
if (fsThis.Bold)
{
sb.Append(" FontWeight=\"Bold\"");
}
else
{
sb.Append(" FontWeight=\"Normal\"");
}
}
if (fsThis.Italic != fsParent.Italic)
{
if (fsThis.Italic)
{
sb.Append(" FontStyle=\"Italic\"");
}
else
{
sb.Append(" FontStyle=\"Normal\"");
}
}
if (fsThis.CF != fsParent.CF)
{
ColorTableEntry entry = converterState.ColorTable.EntryAt((int)fsThis.CF);
if (entry != null && !entry.IsAuto)
{
sb.Append(" Foreground=\"");
sb.Append(entry.Color.ToString());
sb.Append('"');
}
}
// NB: 0x400 (1024) is reserved value for "lidNoProof" - not a real language code.
if (fsThis.LangCur != fsParent.LangCur && fsThis.LangCur > 0 && fsThis.LangCur != 0x400)
{
try
{
CultureInfo ci = new CultureInfo((int)fsThis.LangCur);
sb.Append(" xml:lang=\"");
sb.Append(ci.Name);
sb.Append('"');
}
catch (System.ArgumentException)
{
// Just omit xml:lang tag if this is not a valid value.
}
}
}
internal string StripInvalidChars(string text)
{
if (text == null || text.Length == 0)
{
return text;
}
StringBuilder sb = null;
int i = 0;
for (; i < text.Length; i++)
{
int iStart = i;
for (; i < text.Length; i++)
{
if ((text[i] & 0xF800) == 0xD800) // if surrogate
{
if ((i + 1 == text.Length) // and no trail char
|| ((text[i] & 0xFC00) == 0xDC00) // or low surrogate occurs before high
|| ((text[i + 1] & 0xFC00) != 0xDC00) // or high not followed by low
)
{
break; // then cull this
}
else
{
i++; // move past first word of surrogate, then second at top of loop`
}
}
}
if (iStart != 0 || i != text.Length)
{
if (sb == null)
{
sb = new StringBuilder();
}
if (i != iStart)
{
sb.Append(text, iStart, i - iStart);
}
}
}
if (sb != null)
{
return sb.ToString();
}
else
{
return text;
}
}
internal void AppendXamlEncoded(string text)
{
StringBuilder xamlStringBuilder = new StringBuilder(Xaml);
int index = 0;
while (index < text.Length)
{
int currentIndex = index;
while (currentIndex < text.Length)
{
if (text[currentIndex] < 32
&& text[currentIndex] != '\t')
{
break;
}
if (text[currentIndex] == '&'
|| text[currentIndex] == '>'
|| text[currentIndex] == '<'
|| text[currentIndex] == 0)
{
break;
}
currentIndex++;
}
if (currentIndex != index)
{
ReadOnlySpan<char> substring = text.AsSpan(index, currentIndex - index);
xamlStringBuilder.Append(substring);
}
if (currentIndex < text.Length)
{
if (text[currentIndex] < 32 && text[currentIndex] != '\t')
{
switch (text[currentIndex])
{
case '\f': // formfeed
xamlStringBuilder.Append("&#x");
int ic = (int)text[currentIndex];
xamlStringBuilder.Append(ic.ToString("x", CultureInfo.InvariantCulture));
xamlStringBuilder.Append(';');
break;
default:
// don't care about low ANSI values - not supported by XAML
break;
}
}
else
{
switch (text[currentIndex])
{
case '&': xamlStringBuilder.Append("&"); break;
case '<': xamlStringBuilder.Append("<"); break;
case '>': xamlStringBuilder.Append(">"); break;
case (char)0: break;
}
}
}
index = currentIndex + 1;
}
Xaml = xamlStringBuilder.ToString();
}
internal void AppendXamlPrefix(ConverterState converterState)
{
DocumentNodeArray dna = converterState.DocumentNodeArray;
if (IsHidden)
{
return;
}
if (Type == DocumentNodeType.dnImage)
{
// Append image xaml prefix
AppendImageXamlPrefix();
return;
}
if (Type == DocumentNodeType.dnText || Type == DocumentNodeType.dnInline)
{
AppendInlineXamlPrefix(converterState);
return;
}
StringBuilder xamlStringBuilder = new StringBuilder();
// Do I need to wrap a font around this?
if (IsEmptyNode && RequiresXamlFontProperties())
{
xamlStringBuilder.Append('<');
xamlStringBuilder.Append(XamlNames[(int)DocumentNodeType.dnInline]);
AppendXamlFontProperties(converterState, xamlStringBuilder);
xamlStringBuilder.Append('>');
}
xamlStringBuilder.Append('<');
xamlStringBuilder.Append(GetTagName());
switch (Type)
{
case DocumentNodeType.dnTable:
// See below for writing out table column information
AppendXamlPrefixTableProperties(xamlStringBuilder);
break;
case DocumentNodeType.dnCell:
// Row stores cell properties.
AppendXamlPrefixCellProperties(xamlStringBuilder, dna, converterState);
break;
case DocumentNodeType.dnParagraph:
AppendXamlPrefixParagraphProperties(xamlStringBuilder, converterState);
break;
case DocumentNodeType.dnListItem:
// List margins are handled at the paragraph level
AppendXamlPrefixListItemProperties(xamlStringBuilder);
break;
case DocumentNodeType.dnList:
// List margins are handled at the listitem level
AppendXamlPrefixListProperties(xamlStringBuilder);
break;
case DocumentNodeType.dnHyperlink:
AppendXamlPrefixHyperlinkProperties(xamlStringBuilder);
break;
}
if (IsEmptyNode)
{
xamlStringBuilder.Append(" /");
}
xamlStringBuilder.Append('>');
// Do I need to wrap a font around this?
if (IsEmptyNode && RequiresXamlFontProperties())
{
xamlStringBuilder.Append("</");
xamlStringBuilder.Append(XamlNames[(int)DocumentNodeType.dnInline]);
xamlStringBuilder.Append('>');
}
// Anything after the start tag?
switch (Type)
{
case DocumentNodeType.dnTable:
AppendXamlTableColumnsAfterStartTag(xamlStringBuilder);
break;
}
Xaml = xamlStringBuilder.ToString();
}
private void AppendXamlPrefixTableProperties(StringBuilder xamlStringBuilder)
{
// See below for writing out table column information
if (FormatState.HasRowFormat)
{
if (FormatState.RowFormat.Dir == DirState.DirRTL)
{
xamlStringBuilder.Append(" FlowDirection=\"RightToLeft\"");
}
RowFormat rf = FormatState.RowFormat;
CellFormat cf = rf.RowCellFormat;
xamlStringBuilder.Append(" CellSpacing=\"");
xamlStringBuilder.Append(Converters.TwipToPositiveVisiblePxString(cf.SpacingLeft));
xamlStringBuilder.Append('"');
xamlStringBuilder.Append(" Margin=\"");
xamlStringBuilder.Append(Converters.TwipToPositivePxString(rf.Trleft));
xamlStringBuilder.Append(",0,0,0\"");
}
else
{
xamlStringBuilder.Append(" CellSpacing=\"0\" Margin=\"0,0,0,0\"");
}
}
private void AppendXamlPrefixCellProperties(StringBuilder xamlStringBuilder, DocumentNodeArray dna, ConverterState converterState)
{
Color cToUse = Color.FromArgb(0xff, 0, 0, 0);
// Row stores cell properties.
DocumentNode dnRow = GetParentOfType(DocumentNodeType.dnRow);
Debug.Assert(dnRow != null); // Need row
Debug.Assert(dnRow != null && !dnRow.IsPending); // Row props attached when row is closed
Debug.Assert(dnRow != null && dnRow.FormatState.RowFormat != null);
if (dnRow != null && dnRow.FormatState.HasRowFormat)
{
int nCol = GetCellColumn();
CellFormat cf = dnRow.FormatState.RowFormat.NthCellFormat(nCol);
if (Converters.ColorToUse(converterState, cf.CB, cf.CF, cf.Shading, ref cToUse))
{
xamlStringBuilder.Append(" Background=\"");
xamlStringBuilder.Append(cToUse.ToString(CultureInfo.InvariantCulture));
xamlStringBuilder.Append('"');
}
if (cf.HasBorder)
{
xamlStringBuilder.Append(cf.GetBorderAttributeString(converterState));
}
xamlStringBuilder.Append(cf.GetPaddingAttributeString());
}
else
xamlStringBuilder.Append(" BorderBrush=\"#FF000000\" BorderThickness=\"1,1,1,1\"");
if (ColSpan > 1)
{
xamlStringBuilder.Append(" ColumnSpan=\"");
xamlStringBuilder.Append(ColSpan.ToString(CultureInfo.InvariantCulture));
xamlStringBuilder.Append('"');
}
if (RowSpan > 1)
{
xamlStringBuilder.Append(" RowSpan=\"");
xamlStringBuilder.Append(RowSpan.ToString(CultureInfo.InvariantCulture));
xamlStringBuilder.Append('"');
}
}
private void AppendXamlDir(StringBuilder xamlStringBuilder)
{
if (RequiresXamlDir)
{
if (XamlDir == DirState.DirLTR)
{
xamlStringBuilder.Append(" FlowDirection=\"LeftToRight\"");
}
else
{
xamlStringBuilder.Append(" FlowDirection=\"RightToLeft\"");
}
}
}
private void AppendXamlPrefixParagraphProperties(StringBuilder xamlStringBuilder, ConverterState converterState)
{
Color cToUse = Color.FromArgb(0xff, 0, 0, 0);
FormatState fsThis = FormatState;
if (Converters.ColorToUse(converterState, fsThis.CBPara, fsThis.CFPara, fsThis.ParaShading, ref cToUse))
{
xamlStringBuilder.Append(" Background=\"");
xamlStringBuilder.Append(cToUse.ToString(CultureInfo.InvariantCulture));
xamlStringBuilder.Append('"');
}
// Handle paragraph direction
AppendXamlDir(xamlStringBuilder);
// Handle paragraph margins
xamlStringBuilder.Append(" Margin=\"");
xamlStringBuilder.Append(Converters.TwipToPositivePxString(NearMargin));
xamlStringBuilder.Append(',');
xamlStringBuilder.Append(Converters.TwipToPositivePxString(fsThis.SB));
xamlStringBuilder.Append(',');
xamlStringBuilder.Append(Converters.TwipToPositivePxString(FarMargin));
xamlStringBuilder.Append(',');
xamlStringBuilder.Append(Converters.TwipToPositivePxString(fsThis.SA));
xamlStringBuilder.Append('"');
// FontFamily, Size, Bold, Italic
AppendXamlFontProperties(converterState, xamlStringBuilder);
// Lineheight
// NB: Avalon only supports "lineheight exact" - we're just not going to output it.
//if (fsThis.SL != 0)
//{
// double px = (float)fsThis.SL;
// if (px < 0) px = -px;
// Whether SLMult is on or not is really moot. The value is always defined in twips,
// the UI is the only thing that then interprets this as "multiple", probably when the
// paragraph font is reset.
// xamlStringBuilder.Append(" LineHeight=\"");
// xamlStringBuilder.Append(Converters.TwipToPxString(px));
// xamlStringBuilder.Append('"');
//}
// Indent
if (fsThis.FI != 0)
{
xamlStringBuilder.Append(" TextIndent=\"");
xamlStringBuilder.Append(Converters.TwipToPxString(fsThis.FI));
xamlStringBuilder.Append('"');
}
// Handle paragraph alignment
if (fsThis.HAlign != HAlign.AlignDefault)
{
xamlStringBuilder.Append(" TextAlignment=\"");
xamlStringBuilder.Append(Converters.AlignmentToString(fsThis.HAlign, fsThis.DirPara));
xamlStringBuilder.Append('"');
}
// Handle paragraph borders
if (fsThis.HasParaBorder)
{
xamlStringBuilder.Append(fsThis.GetBorderAttributeString(converterState));
}
}
private void AppendXamlPrefixListItemProperties(StringBuilder xamlStringBuilder)
{
// List margins are handled here normally.
// NB: Avalon doesn't render list markers if margin is zero. Enforce a minimum indent in order
// to ensure the marker is visible.
long lMargin = NearMargin;
if (lMargin < 360 && this.GetListDepth() == 1)
{
DocumentNode dnList = Parent;
if (dnList != null && dnList.FormatState.Marker != MarkerStyle.MarkerHidden)
{
lMargin = 360;
}
}
xamlStringBuilder.Append(" Margin=\"");
xamlStringBuilder.Append(Converters.TwipToPositivePxString(lMargin));
xamlStringBuilder.Append(",0,0,0\"");
// Handle direction
AppendXamlDir(xamlStringBuilder);
}
private void AppendXamlPrefixListProperties(StringBuilder xamlStringBuilder)
{
// List margins are handled at the listitem level
xamlStringBuilder.Append(" Margin=\"0,0,0,0\"");
xamlStringBuilder.Append(" Padding=\"0,0,0,0\"");
// Marker style
xamlStringBuilder.Append(" MarkerStyle=\"");
xamlStringBuilder.Append(Converters.MarkerStyleToString(FormatState.Marker));
xamlStringBuilder.Append('"');
// Note that we don't allow a value of zero here, since XAML doesn't support it.
if (FormatState.StartIndex > 0 && FormatState.StartIndex != 1)
{
xamlStringBuilder.Append(" StartIndex=\"");
xamlStringBuilder.Append(FormatState.StartIndex.ToString(CultureInfo.InvariantCulture));
xamlStringBuilder.Append('"');
}
// Handle direction
AppendXamlDir(xamlStringBuilder);
}
private void AppendXamlPrefixHyperlinkProperties(StringBuilder xamlStringBuilder)
{
if (NavigateUri != null && NavigateUri.Length > 0)
{
xamlStringBuilder.Append(" NavigateUri=\"");
xamlStringBuilder.Append(Converters.StringToXMLAttribute(NavigateUri));
xamlStringBuilder.Append('"');
}
}
private void AppendXamlTableColumnsAfterStartTag(StringBuilder xamlStringBuilder)
{
if (ColumnStateArray != null && ColumnStateArray.Count > 0)
{
xamlStringBuilder.Append("<Table.Columns>");
long prevX = 0;
if (FormatState.HasRowFormat)
{
prevX = FormatState.RowFormat.Trleft;
}
for (int i = 0; i < ColumnStateArray.Count; i++)
{
ColumnState cs = ColumnStateArray.EntryAt(i);
long width = cs.CellX - prevX;
if (width <= 0)
{
width = 1;
}
prevX = cs.CellX;
xamlStringBuilder.Append("<TableColumn Width=\"");
xamlStringBuilder.Append(Converters.TwipToPxString(width));
xamlStringBuilder.Append("\" />");
}
xamlStringBuilder.Append("</Table.Columns>");
}
}
internal void AppendXamlPostfix(ConverterState converterState)
{
if (IsHidden)
{
return;
}
// Empty tag terminated above
if (IsEmptyNode)
{
return;
}
if (Type == DocumentNodeType.dnImage)
{
// Append image xaml postfix
AppendImageXamlPostfix();
return;
}
if (Type == DocumentNodeType.dnText || Type == DocumentNodeType.dnInline)
{
AppendInlineXamlPostfix(converterState);
return;
}
Xaml = $"{Xaml}</{GetTagName()}>{(IsBlock ? "\r\n" : "")}";
}
internal void AppendInlineXamlPrefix(ConverterState converterState)
{
StringBuilder xamlStringBuilder = new StringBuilder();
FormatState fsThis = this.FormatState;
FormatState fsParent = ParentFormatStateForFont;
// Wrap any text with formatting tags.
xamlStringBuilder.Append("<Span");
AppendXamlDir(xamlStringBuilder);
if (fsThis.CB != fsParent.CB)
{
ColorTableEntry entry = converterState.ColorTable.EntryAt((int)fsThis.CB);
if (entry != null && !entry.IsAuto)
{
xamlStringBuilder.Append(" Background=\"");
xamlStringBuilder.Append(entry.Color.ToString());
xamlStringBuilder.Append('"');
}
}
AppendXamlFontProperties(converterState, xamlStringBuilder);
// NB: Avalon does not support the RTF notion of Expand
//if (fsThis.Expand != fsParent.Expand)
//{
// if (fsThis.Expand > 0)
// {
// xamlStringBuilder.Append(" FontStretch=\"Expanded\"");
// }
// else
// {
// xamlStringBuilder.Append(" FontStretch=\"Condensed\"");
// }
//}
if (fsThis.Super != fsParent.Super)
{
xamlStringBuilder.Append(" Typography.Variants=\"Superscript\"");
}
if (fsThis.Sub != fsParent.Sub)
{
xamlStringBuilder.Append(" Typography.Variants=\"Subscript\"");
}
xamlStringBuilder.Append('>');
Xaml = xamlStringBuilder.ToString();
}
internal void AppendInlineXamlPostfix(ConverterState converterState)
{
Xaml = $"{Xaml}</Span>";
}
internal void AppendImageXamlPrefix()
{
Xaml = "<InlineUIContainer>";
}
internal void AppendImageXamlPostfix()
{
Xaml += "</InlineUIContainer>";
}
internal bool IsAncestorOf(DocumentNode documentNode)
{
int parentIndex = Index;
int parentLastChild = Index + ChildCount;
return documentNode.Index > parentIndex && documentNode.Index <= parentLastChild;
}
internal bool IsLastParagraphInCell()
{
DocumentNodeArray dna = DNA;
if (Type != DocumentNodeType.dnParagraph)
return false;
DocumentNode dnCell = GetParentOfType(DocumentNodeType.dnCell);
if (dnCell == null)
{
return false;
}
int nFirst = dnCell.Index + 1;
int nLast = dnCell.Index + dnCell.ChildCount;
for (; nFirst <= nLast; nLast--)
{
DocumentNode dn = dna.EntryAt(nLast);
if (dn == this)
{
return true;
}
if (dn.IsBlock)
{
return false;
}
}
return false;
}
internal DocumentNodeArray GetTableRows()
{
DocumentNodeArray dna = DNA;
DocumentNodeArray retArray = new DocumentNodeArray();
if (Type == DocumentNodeType.dnTable)
{
int nStart = this.Index + 1;
int nLast = this.Index + this.ChildCount;
for (; nStart <= nLast; nStart++)
{
DocumentNode dnRow = dna.EntryAt(nStart);
if (dnRow.Type == DocumentNodeType.dnRow && this == dnRow.GetParentOfType(DocumentNodeType.dnTable))
{
retArray.Push(dnRow);
}
}
}
return retArray;
}
internal DocumentNodeArray GetRowsCells()
{
DocumentNodeArray dna = DNA;
DocumentNodeArray retArray = new DocumentNodeArray();
if (Type == DocumentNodeType.dnRow)
{
int nStart = this.Index + 1;
int nLast = this.Index + this.ChildCount;
for (; nStart <= nLast; nStart++)
{
DocumentNode dnCell = dna.EntryAt(nStart);
if (dnCell.Type == DocumentNodeType.dnCell && this == dnCell.GetParentOfType(DocumentNodeType.dnRow))
{
retArray.Push(dnCell);
}
}
}
return retArray;
}
internal int GetCellColumn()
{
DocumentNodeArray dna = DNA;
int nCol = 0;
if (Type == DocumentNodeType.dnCell)
{
DocumentNode dnRow = this.GetParentOfType(DocumentNodeType.dnRow);
if (dnRow != null)
{
int nStart = dnRow.Index + 1;
int nLast = dnRow.Index + dnRow.ChildCount;
for (; nStart <= nLast; nStart++)
{
DocumentNode dnCell = dna.EntryAt(nStart);
if (dnCell == this)
{
break;
}
if (dnCell.Type == DocumentNodeType.dnCell && dnCell.GetParentOfType(DocumentNodeType.dnRow) == dnRow)
{
nCol++;
}
}
}
}
return nCol;
}
internal ColumnStateArray ComputeColumns()
{
DocumentNodeArray dna = DNA;
Debug.Assert(Type == DocumentNodeType.dnTable);
DocumentNodeArray dnaRows = GetTableRows();
ColumnStateArray cols = new ColumnStateArray();
for (int i = 0; i < dnaRows.Count; i++)
{
DocumentNode dnRow = dnaRows.EntryAt(i);
RowFormat rf = dnRow.FormatState.RowFormat;
long prevCellX = 0;
for (int j = 0; j < rf.CellCount; j++)
{
CellFormat cf = rf.NthCellFormat(j);
bool bHandled = false;
long prevColX = 0;
// Ignore merged cells
if (cf.IsHMerge)
{
continue;
}
for (int k = 0; k < cols.Count; k++)
{
ColumnState cs = (ColumnState)cols[k];
if (cs.CellX == cf.CellX)
{
if (!cs.IsFilled && prevColX == prevCellX)
{
cs.IsFilled = true;
}
bHandled = true;
break;
}
else if (cs.CellX > cf.CellX)
{
// Hmmm, need to insert a new cell here
ColumnState csNew = new ColumnState();
csNew.Row = dnRow;
csNew.CellX = cf.CellX;
csNew.IsFilled = (prevColX == prevCellX);
cols.Insert(k, csNew);
bHandled = true;
break;
}
prevColX = cs.CellX;
}
// New cell at the end
if (!bHandled)
{
ColumnState csNew = new ColumnState();
csNew.Row = dnRow;
csNew.CellX = cf.CellX;
csNew.IsFilled = (prevColX == prevCellX);
cols.Add(csNew);
}
prevCellX = cf.CellX;
}
}
return cols;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
#endregion Private Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal bool IsInline
{
get
{
return _type == DocumentNodeType.dnText
|| _type == DocumentNodeType.dnInline
|| _type == DocumentNodeType.dnImage
|| _type == DocumentNodeType.dnLineBreak
|| _type == DocumentNodeType.dnListText
|| _type == DocumentNodeType.dnHyperlink;
}
}
internal bool IsBlock
{
get
{
return _type == DocumentNodeType.dnParagraph
|| _type == DocumentNodeType.dnList
|| _type == DocumentNodeType.dnListItem
|| _type == DocumentNodeType.dnTable
|| _type == DocumentNodeType.dnTableBody
|| _type == DocumentNodeType.dnRow
|| _type == DocumentNodeType.dnCell
|| _type == DocumentNodeType.dnSection
|| _type == DocumentNodeType.dnFigure
|| _type == DocumentNodeType.dnFloater;
}
}
internal bool IsEmptyNode
{
get
{
return _type == DocumentNodeType.dnLineBreak;
}
}
internal bool IsHidden
{
get
{
return _type == DocumentNodeType.dnFieldBegin
|| _type == DocumentNodeType.dnFieldEnd
|| _type == DocumentNodeType.dnShape
|| _type == DocumentNodeType.dnListText;
}
}
internal bool IsWhiteSpace
{
get
{
// Can't compute this on a terminated node, since non-text-data has been appended for properties.
if (IsTerminated)
{
return false;
}
if (_type == DocumentNodeType.dnText)
{
string textdata = Xaml.Trim();
return textdata.Length == 0;
}
return false;
}
}
internal bool IsPending
{
get
{
// Can't be pending if no longer in main document array
return Index >= 0 && _bPending;
}
set
{
_bPending = value;
}
}
internal bool IsTerminated
{
get
{
return _bTerminated;
}
set
{
_bTerminated = value;
}
}
internal bool IsMatched
{
get
{
// This is only relevant for types that need matching.
if (Type == DocumentNodeType.dnFieldBegin)
{
return _bMatched;
}
// Otherwise, always true.
return true;
}
set
{
_bMatched = value;
}
}
internal bool IsTrackedAsOpen
{
get
{
if (Index < 0)
{
return false;
}
if (Type == DocumentNodeType.dnFieldEnd)
{
return false;
}
if (IsPending && !IsTerminated)
{
return true;
}
if (!IsMatched)
{
return true;
}
return false;
}
}
internal bool HasMarkerContent
{
get
{
return _bHasMarkerContent;
}
set
{
_bHasMarkerContent = value;
}
}
internal bool IsNonEmpty
{
get
{
return ChildCount > 0 || Xaml != null;
}
}
internal string ListLabel
{
get
{
return _sCustom;
}
set
{
_sCustom = value;
}
}
internal long VirtualListLevel
{
get
{
return _nVirtualListLevel;
}
set
{
_nVirtualListLevel = value;
}
}
internal string NavigateUri
{
get
{
return _sCustom;
}
set
{
_sCustom = value;
}
}
internal DocumentNodeType Type
{
get
{
return _type;
}
}
internal FormatState FormatState
{
get
{
return _formatState;
}
set
{
_formatState = value;
}
}
internal FormatState ParentFormatStateForFont
{
get
{
DocumentNode dnPa = Parent;
// Hyperlink doesn't record relevant font info
if (dnPa != null && dnPa.Type == DocumentNodeType.dnHyperlink)
{
dnPa = dnPa.Parent;
}
if (Type == DocumentNodeType.dnParagraph || dnPa == null)
{
return FormatState.EmptyFormatState;
}
return dnPa.FormatState;
}
}
internal int ChildCount
{
get
{
return _childCount;
}
set
{
Debug.Assert(value >= 0);
Debug.Assert(!IsPending);
if (value >= 0)
{
_childCount = value;
}
}
}
internal int Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
internal DocumentNodeArray DNA
{
get
{
return _dna;
}
set
{
_dna = value;
}
}
internal int LastChildIndex
{
get
{
return Index + ChildCount;
}
}
internal DocumentNode ClosedParent
{
get
{
return _parent;
}
}
internal DocumentNode Parent
{
get
{
if (_parent == null && DNA != null)
{
return DNA.GetOpenParentWhileParsing(this);
}
return _parent;
}
set
{
Debug.Assert(value == null || !value.IsPending);
_parent = value;
}
}
internal string Xaml
{
get
{
return _xaml;
}
set
{
_xaml = value;
}
}
internal StringBuilder Content
{
get
{
return _contentBuilder;
}
}
internal int RowSpan
{
get
{
return _nRowSpan;
}
set
{
_nRowSpan = value;
}
}
internal int ColSpan
{
get
{
return _nColSpan;
}
set
{
_nColSpan = value;
}
}
internal ColumnStateArray ColumnStateArray
{
get
{
return _csa;
}
set
{
_csa = value;
}
}
internal DirState XamlDir
{
get
{
// Inline's easy
if (IsInline)
{
return FormatState.DirChar;
}
// We only have valid direction on table, list and paragraph.
if (Type == DocumentNodeType.dnTable)
{
if (FormatState.HasRowFormat)
{
return FormatState.RowFormat.Dir;
}
return ParentXamlDir;
}
else if (Type == DocumentNodeType.dnList || Type == DocumentNodeType.dnParagraph)
{
return FormatState.DirPara;
}
else
{
for (DocumentNode dnPa = Parent; dnPa != null; dnPa = dnPa.Parent)
{
switch (dnPa.Type)
{
case DocumentNodeType.dnList:
case DocumentNodeType.dnParagraph:
case DocumentNodeType.dnTable:
return dnPa.XamlDir;
}
}
return DirState.DirLTR;
}
}
}
internal DirState ParentXamlDir
{
get
{
return (Parent == null) ? DirState.DirLTR : Parent.XamlDir;
}
}
internal bool RequiresXamlDir
{
get
{
return XamlDir != ParentXamlDir;
}
}
internal long NearMargin
{
get
{
return ParentXamlDir == DirState.DirLTR ? FormatState.LI : FormatState.RI;
}
set
{
if (ParentXamlDir == DirState.DirLTR)
{
FormatState.LI = value;
}
else
{
FormatState.RI = value;
}
}
}
internal long FarMargin
{
get
{
return ParentXamlDir == DirState.DirLTR ? FormatState.RI : FormatState.LI;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Fields
//
//------------------------------------------------------
#region Internal Fields
private bool _bPending;
private bool _bTerminated;
private DocumentNodeType _type;
private FormatState _formatState;
private string _xaml;
private StringBuilder _contentBuilder;
// Used for "tree" semantics
private int _childCount;
private int _index;
private DocumentNode _parent;
private DocumentNodeArray _dna;
// Custom fields for specific node types
// Tables
private ColumnStateArray _csa;
// Cells
private int _nRowSpan;
private int _nColSpan;
// Lists
private string _sCustom; // Also used for Hyperlink
private long _nVirtualListLevel;
// ListText
private bool _bHasMarkerContent;
// Fields
private bool _bMatched;
#endregion Internal Fields
}
internal class ColumnState
{
internal ColumnState()
{
_nCellX = 0;
_row = null;
_fFilled = false;
}
internal long CellX
{
get
{
return _nCellX;
}
set
{
_nCellX = value;
}
}
internal DocumentNode Row
{
get
{
return _row;
}
set
{
_row = value;
}
}
internal bool IsFilled
{
get
{
return _fFilled;
}
set
{
_fFilled = value;
}
}
private long _nCellX;
private DocumentNode _row;
private bool _fFilled;
}
internal class ColumnStateArray : ArrayList
{
internal ColumnStateArray()
: base(20)
{
}
internal ColumnState EntryAt(int i)
{
return (ColumnState)this[i];
}
internal int GetMinUnfilledRowIndex()
{
int nUnfilledRowIndex = -1;
for (int i = 0; i < Count; i++)
{
ColumnState cs = EntryAt(i);
if (!cs.IsFilled && (nUnfilledRowIndex < 0 || nUnfilledRowIndex > cs.Row.Index))
{
// Don't split at row that is traversed by a row-spanning cell.
if (!cs.Row.FormatState.RowFormat.IsVMerge)
{
nUnfilledRowIndex = cs.Row.Index;
}
}
}
Debug.Assert(nUnfilledRowIndex != 0);
return nUnfilledRowIndex;
}
}
/// <summary>
/// class DocumentNodeArray:
/// This array represents a depth-first walk through the tree of nodes. Each node records its current
/// index in the array (for ease of mapping back to the array) as well as the number of descendants
/// (confusingly called ChildCount). A Parent pointer is also maintained, but this is essentially a
/// cache - the real structure is specified by the implicit ordering and the ChildCount value.
/// While the array is being constructed, nodes may be marked as "Pending". The ChildCount of pending
/// nodes is not accurate. ChildCount only becomes valid when a node is "Closed".
/// </summary>
internal class DocumentNodeArray : ArrayList
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
internal DocumentNodeArray()
: base(100)
{
_fMain = false;
_dnaOpen = null;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal DocumentNode EntryAt(int nAt)
{
return (DocumentNode)this[nAt];
}
internal void Push(DocumentNode documentNode)
{
InsertNode(Count, documentNode);
}
internal DocumentNode Pop()
{
DocumentNode documentNode = Top;
if (Count > 0)
{
Excise(Count - 1, 1);
}
return documentNode;
}
internal DocumentNode TopPending()
{
for (int i = Count - 1; i >= 0; i--)
{
DocumentNode dn = EntryAt(i);
if (dn.IsPending)
{
return dn;
}
}
return null;
}
internal bool TestTop(DocumentNodeType documentNodeType)
{
return ((Count > 0) && (EntryAt(Count - 1).Type == documentNodeType));
}
internal void PreCoalesceChildren(ConverterState converterState, int nStart, bool bChild)
{
// We process tables twice to handle first colspan, then rowspan
DocumentNodeArray dnaTables = new DocumentNodeArray();
bool fVMerged = false;
// Try to move paragraph margin to containing list items
DocumentNode dnCoalesce = EntryAt(nStart);
int nChild = dnCoalesce.ChildCount;
Debug.Assert(nStart + nChild < Count);
if (nStart + nChild >= Count)
{
nChild = Count - nStart - 1;
}
int nEnd = nStart + nChild;
// If bChild specified, we don't process parent
if (bChild)
{
nStart++;
}
// This is vaguely N^2 in the sense that for each list item, I process all containing paragraphs,
// including ones contained in other list items. But it's only a problem for very deep, very long
// lists, so we can live with it.
for (int nAt = nStart; nAt <= nEnd; nAt++)
{
DocumentNode dn = EntryAt(nAt);
// Inline direction merging
if (dn.IsInline && dn.RequiresXamlDir && dn.ClosedParent != null)
{
int nnAt = nAt + 1;
for (; nnAt <= nEnd; nnAt++)
{
DocumentNode dnn = EntryAt(nnAt);
if (!dnn.IsInline
|| dnn.Type == DocumentNodeType.dnHyperlink
|| dnn.FormatState.DirChar != dn.FormatState.DirChar
|| dnn.ClosedParent != dn.ClosedParent)
{
break;
}
}
int nChildHere = nnAt - nAt;
if (nChildHere > 1)
{
DocumentNode dnNewDir = new DocumentNode(DocumentNodeType.dnInline);
dnNewDir.FormatState = new FormatState(dn.Parent.FormatState);
dnNewDir.FormatState.DirChar = dn.FormatState.DirChar;
InsertChildAt(dn.ClosedParent, dnNewDir, nAt, nChildHere);
// Adjust the loop end to account for the newly inserted element
nEnd += 1;
}
}
else if (dn.Type == DocumentNodeType.dnListItem)
{
PreCoalesceListItem(dn);
}
else if (dn.Type == DocumentNodeType.dnList)
{
PreCoalesceList(dn);
}
else if (dn.Type == DocumentNodeType.dnTable)
{
dnaTables.Add(dn);
nEnd += PreCoalesceTable(dn);
}
// Compute colspan
else if (dn.Type == DocumentNodeType.dnRow)
{
PreCoalesceRow(dn, ref fVMerged);
}
}
// Process tables to examine rowspan
if (fVMerged)
{
ProcessTableRowSpan(dnaTables);
}
}
internal void CoalesceChildren(ConverterState converterState, int nStart)
{
Debug.Assert(Count == 0 || (nStart >= 0 && nStart < Count));
if (nStart >= Count || nStart < 0)
{
return;
}
// Do some fixups to match semantics for more complicated constructs
PreCoalesceChildren(converterState, nStart, false);
DocumentNode dnCoalesce = EntryAt(nStart);
int nChild = dnCoalesce.ChildCount;
Debug.Assert(nStart + nChild < Count);
if (nStart + nChild >= Count)
{
nChild = Count - nStart - 1;
}
int nEnd = nStart + nChild;
for (int nAt = nEnd; nAt >= nStart; nAt--)
{
DocumentNode dn = EntryAt(nAt);
if (dn.ChildCount == 0)
{
dn.Terminate(converterState);
}
else
{
Debug.Assert(nAt + dn.ChildCount <= nEnd);
Debug.Assert(!dn.IsTerminated);
dn.AppendXamlPrefix(converterState);
StringBuilder xamlBuilder = new StringBuilder(dn.Xaml);
int nChildrenHere = dn.ChildCount;
int nEndHere = nAt + nChildrenHere;
for (int i = nAt + 1; i <= nEndHere; i++)
{
DocumentNode dnChild = EntryAt(i);
Debug.Assert(dnChild.ChildCount == 0 && dnChild.IsTerminated);
xamlBuilder.Append(dnChild.Xaml);
}
dn.Xaml = xamlBuilder.ToString();
dn.AppendXamlPostfix(converterState);
dn.IsTerminated = true;
Excise(nAt + 1, nChildrenHere);
nEnd -= nChildrenHere;
AssertTreeInvariants();
}
// Zero out spanned columns
if (dn.ColSpan == 0)
{
dn.Xaml = string.Empty;
}
}
}
internal void CoalesceOnlyChildren(ConverterState converterState, int nStart)
{
Debug.Assert(Count == 0 || (nStart >= 0 && nStart < Count));
if (nStart >= Count || nStart < 0)
{
return;
}
// Do some fixups to match semantics for more complicated constructs
PreCoalesceChildren(converterState, nStart, true);
DocumentNode dnCoalesce = EntryAt(nStart);
int nChild = dnCoalesce.ChildCount;
Debug.Assert(nStart + nChild < Count);
if (nStart + nChild >= Count)
{
nChild = Count - nStart - 1;
}
int nEnd = nStart + nChild;
for (int nAt = nEnd; nAt >= nStart; nAt--)
{
DocumentNode dn = EntryAt(nAt);
if (dn.ChildCount == 0 && nAt != nStart)
{
dn.Terminate(converterState);
}
else if (dn.ChildCount > 0)
{
Debug.Assert(nAt + dn.ChildCount <= nEnd);
Debug.Assert(!dn.IsTerminated);
if (nAt != nStart)
{
dn.AppendXamlPrefix(converterState);
}
StringBuilder xamlBuilder = new StringBuilder(dn.Xaml);
int nChildrenHere = dn.ChildCount;
int nEndHere = nAt + nChildrenHere;
for (int i = nAt + 1; i <= nEndHere; i++)
{
DocumentNode dnChild = EntryAt(i);
Debug.Assert(dnChild.ChildCount == 0 && dnChild.IsTerminated);
xamlBuilder.Append(dnChild.Xaml);
}
dn.Xaml = xamlBuilder.ToString();
if (nAt != nStart)
{
dn.AppendXamlPostfix(converterState);
dn.IsTerminated = true;
}
Excise(nAt + 1, nChildrenHere);
nEnd -= nChildrenHere;
}
}
}
internal void CoalesceAll(ConverterState converterState)
{
for (int nAt = 0; nAt < Count; nAt++)
{
CoalesceChildren(converterState, nAt);
}
}
internal void CloseAtHelper(int index, int nChildCount)
{
Debug.Assert(Count == 0 || (index >= 0 && index < Count));
Debug.Assert(index + nChildCount < Count);
if (index >= Count || index < 0 || index + nChildCount >= Count)
{
return;
}
DocumentNode dnClose = EntryAt(index);
if (!dnClose.IsPending)
{
return;
}
// Mark this as closed
dnClose.IsPending = false;
dnClose.ChildCount = nChildCount;
int nAt = index + 1;
int nEnd = index + dnClose.ChildCount;
while (nAt <= nEnd)
{
DocumentNode dn = EntryAt(nAt);
dn.Parent = dnClose;
nAt += dn.ChildCount + 1;
}
}
internal void CloseAt(int index)
{
Debug.Assert(Count == 0 || (index >= 0 && index < Count));
if (index >= Count || index < 0)
{
return;
}
DocumentNode dnClose = EntryAt(index);
if (!dnClose.IsPending)
{
return;
}
AssertTreeInvariants();
AssertTreeSemanticInvariants();
// Make sure everything after its start is closed.
for (int i = Count - 1; i > index; i--)
{
DocumentNode dn = EntryAt(i);
if (dn.IsPending)
{
CloseAt(i);
}
}
// Set up child/parent relationship
CloseAtHelper(index, Count - index - 1);
AssertTreeInvariants();
AssertTreeSemanticInvariants();
}
internal void AssertTreeInvariants()
{
if (Invariant.Strict)
{
for (int nAt = 0; nAt < Count; nAt++)
{
DocumentNode dn = EntryAt(nAt);
for (int i = nAt + 1; i <= dn.LastChildIndex; i++)
{
Debug.Assert(EntryAt(i).ClosedParent != null);
}
Debug.Assert(nAt + dn.ChildCount < Count);
for (DocumentNode dnPa = dn.Parent; dnPa != null; dnPa = dnPa.Parent)
{
Debug.Assert(dnPa.IsPending || (nAt > dnPa.Index && nAt <= dnPa.Index + dnPa.ChildCount));
}
}
}
}
internal void AssertTreeSemanticInvariants()
{
if (Invariant.Strict)
{
for (int nAt = 0; nAt < Count; nAt++)
{
DocumentNode dn = EntryAt(nAt);
DocumentNode dnPa = dn.Parent;
switch (dn.Type)
{
case DocumentNodeType.dnTableBody:
Debug.Assert(dnPa != null && dnPa.Type == DocumentNodeType.dnTable);
break;
case DocumentNodeType.dnRow:
Debug.Assert(dnPa != null && dnPa.Type == DocumentNodeType.dnTableBody);
break;
case DocumentNodeType.dnCell:
Debug.Assert(dnPa != null && dnPa.Type == DocumentNodeType.dnRow);
break;
case DocumentNodeType.dnListItem:
Debug.Assert(dnPa != null && dnPa.Type == DocumentNodeType.dnList);
break;
}
}
}
}
internal void CloseAll()
{
for (int nAt = 0; nAt < Count; nAt++)
{
if (EntryAt(nAt).IsPending)
{
CloseAt(nAt);
break;
}
}
}
internal int CountOpenNodes(DocumentNodeType documentNodeType)
{
int nOpen = 0;
if (_dnaOpen != null)
{
_dnaOpen.CullOpen();
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.IsPending)
{
if (dn.Type == documentNodeType)
{
nOpen++;
}
// Shape blocks nesting
else if (dn.Type == DocumentNodeType.dnShape)
{
break;
}
}
}
}
return nOpen;
}
internal int CountOpenCells()
{
return CountOpenNodes(DocumentNodeType.dnCell);
}
internal DocumentNode GetOpenParentWhileParsing(DocumentNode dn)
{
if (_dnaOpen != null)
{
_dnaOpen.CullOpen();
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dnPa = _dnaOpen.EntryAt(i);
if (dnPa.IsPending && dnPa.Index < dn.Index)
{
return dnPa;
}
}
}
return null;
}
internal DocumentNodeType GetTableScope()
{
if (_dnaOpen != null)
{
_dnaOpen.CullOpen();
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.IsPending)
{
if (dn.Type == DocumentNodeType.dnTable
|| dn.Type == DocumentNodeType.dnTableBody
|| dn.Type == DocumentNodeType.dnRow
|| dn.Type == DocumentNodeType.dnCell)
{
return dn.Type;
}
// Shape blocks table structure
else if (dn.Type == DocumentNodeType.dnShape)
{
return DocumentNodeType.dnParagraph;
}
}
}
}
return DocumentNodeType.dnParagraph;
}
internal MarkerList GetOpenMarkerStyles()
{
MarkerList ml = new MarkerList();
if (_dnaOpen != null)
{
_dnaOpen.CullOpen();
int nShape = 0;
for (int i = 0; i < _dnaOpen.Count; i++)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.IsPending && dn.Type == DocumentNodeType.dnShape)
{
nShape = i + 1;
}
}
for (int i = nShape; i < _dnaOpen.Count; i++)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.IsPending && dn.Type == DocumentNodeType.dnList)
{
ml.AddEntry(dn.FormatState.Marker, dn.FormatState.ILS,
dn.FormatState.StartIndex,
dn.FormatState.StartIndexDefault, dn.VirtualListLevel);
}
}
}
return ml;
}
internal MarkerList GetLastMarkerStyles(MarkerList mlHave, MarkerList mlWant)
{
MarkerList ml = new MarkerList();
if (mlHave.Count > 0 || mlWant.Count == 0)
{
return ml;
}
bool bAllBullet = true;
for (int i = Count - 1; i >= 0; i--)
{
DocumentNode dn = EntryAt(i);
// Don't reopen a list across a table.
if (dn.Type == DocumentNodeType.dnCell || dn.Type == DocumentNodeType.dnTable)
{
break;
}
if (dn.Type == DocumentNodeType.dnListItem)
{
// Don't open a list item in a closed table.
DocumentNode dnCell = dn.GetParentOfType(DocumentNodeType.dnCell);
if (dnCell != null && !dnCell.IsPending)
{
break;
}
// Ignore list items in shapes - note the continue since these didn't effect list continuation.
DocumentNode dnShape = dn.GetParentOfType(DocumentNodeType.dnShape);
if (dnShape != null && !dnShape.IsPending)
{
continue;
}
// OK, gather up the list structure that I'm potentially reopening.
for (DocumentNode dnList = dn.Parent; dnList != null; dnList = dnList.Parent)
{
// Note that I'm building this list up in the reverse order of GetOpenMarkerStyles
if (dnList.Type == DocumentNodeType.dnList)
{
MarkerListEntry mle = new MarkerListEntry();
mle.Marker = dnList.FormatState.Marker;
mle.StartIndexOverride = dnList.FormatState.StartIndex;
mle.StartIndexDefault = dnList.FormatState.StartIndexDefault;
mle.VirtualListLevel = dnList.VirtualListLevel;
mle.ILS = dnList.FormatState.ILS;
ml.Insert(0, mle);
if (mle.Marker != MarkerStyle.MarkerBullet)
{
bAllBullet = false;
}
}
}
break;
}
}
// If all bullets at one level, don't do the continuation thing for simpler content generation.
if (ml.Count == 1 && bAllBullet)
{
ml.RemoveRange(0, 1);
}
return ml;
}
internal void OpenLastList()
{
for (int i = Count - 1; i >= 0; i--)
{
DocumentNode dn = EntryAt(i);
if (dn.Type == DocumentNodeType.dnListItem)
{
// Don't do this for lists in shapes.
DocumentNode dnShape = dn.GetParentOfType(DocumentNodeType.dnShape);
if (dnShape != null && !dnShape.IsPending)
{
continue;
}
// Make all this pending.
for (DocumentNode dnPa = dn; dnPa != null; dnPa = dnPa.Parent)
{
if (dnPa.Type == DocumentNodeType.dnList || dnPa.Type == DocumentNodeType.dnListItem)
{
dnPa.IsPending = true;
_dnaOpen.InsertOpenNode(dnPa);
}
}
break;
}
}
}
internal void OpenLastCell()
{
// Be careful about nested cells - I want to open the last cell for the table/body/row that is
// currently pending, not the last cell in the depth-first walk of the tree. So first find
// the pending table scope.
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.IsPending)
{
if (dn.Type == DocumentNodeType.dnCell)
{
// Nothing to do!
return;
}
else if (dn.Type == DocumentNodeType.dnTable
|| dn.Type == DocumentNodeType.dnTableBody
|| dn.Type == DocumentNodeType.dnRow)
{
// OK, now find the cell
for (int j = Count - 1; j >= 0; j--)
{
DocumentNode ddn = EntryAt(j);
// Yikes, better find a child first.
if (ddn == dn)
{
Debug.Assert(false);
return;
}
if (ddn.Type == DocumentNodeType.dnCell && ddn.GetParentOfType(dn.Type) == dn)
{
for (DocumentNode dnPa = ddn; dnPa != null && dnPa != dn; dnPa = dnPa.Parent)
{
dnPa.IsPending = true;
_dnaOpen.InsertOpenNode(dnPa);
}
return;
}
}
}
}
}
}
internal int FindPendingFrom(DocumentNodeType documentNodeType, int nStart, int nLow)
{
if (_dnaOpen != null)
{
_dnaOpen.CullOpen();
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.Index > nStart)
{
continue;
}
if (dn.Index <= nLow)
{
break;
}
if (dn.IsPending)
{
if (dn.Type == documentNodeType)
{
return dn.Index;
}
// Don't return pending elements across shape boundaries
else if (dn.Type == DocumentNodeType.dnShape)
{
break;
}
}
}
}
return -1;
}
internal int FindPending(DocumentNodeType documentNodeType, int nLow)
{
return FindPendingFrom(documentNodeType, Count - 1, nLow);
}
internal int FindPending(DocumentNodeType documentNodeType)
{
return FindPending(documentNodeType, -1);
}
internal int FindUnmatched(DocumentNodeType dnType)
{
if (_dnaOpen != null)
{
for (int i = _dnaOpen.Count - 1; i >= 0; i--)
{
DocumentNode dn = _dnaOpen.EntryAt(i);
if (dn.Type == dnType && !dn.IsMatched)
{
return dn.Index;
}
}
}
return -1;
}
internal void EstablishTreeRelationships()
{
// Record indices
int i;
for (i = 0; i < Count; i++)
{
EntryAt(i).Index = i;
}
for (i = 1; i < Count; i++)
{
DocumentNode dnThis = EntryAt(i);
DocumentNode dnPrev = EntryAt(i - 1);
// If prev isn't my parent, walk up its parent chain to find my parent
if (dnPrev.ChildCount == 0)
{
for (dnPrev = dnPrev.Parent; dnPrev != null; dnPrev = dnPrev.Parent)
{
if (dnPrev.IsAncestorOf(dnThis))
{
break;
}
}
}
dnThis.Parent = dnPrev;
}
}
internal void CullOpen()
{
int i = Count - 1;
for (; i >= 0; i--)
{
DocumentNode dn = EntryAt(i);
if (dn.Index >= 0 && dn.IsTrackedAsOpen)
{
break;
}
}
int nCull = Count - (i + 1);
if (nCull > 0)
{
RemoveRange(i + 1, nCull);
}
}
internal void InsertOpenNode(DocumentNode dn)
{
CullOpen();
int i = Count;
for (; i > 0; i--)
{
if (dn.Index > EntryAt(i - 1).Index)
{
break;
}
}
Insert(i, dn);
}
internal void InsertNode(int nAt, DocumentNode dn)
{
Insert(nAt, dn);
// Match sure Index values remain up-to-date.
if (_fMain)
{
dn.Index = nAt;
dn.DNA = this;
for (nAt++; nAt < Count; nAt++)
{
EntryAt(nAt).Index = nAt;
}
// Track open nodes
if (dn.IsTrackedAsOpen)
{
if (_dnaOpen == null)
{
_dnaOpen = new DocumentNodeArray();
}
_dnaOpen.InsertOpenNode(dn);
}
}
}
internal void InsertChildAt(DocumentNode dnParent, DocumentNode dnNew, int nInsertAt, int nChild)
{
Debug.Assert(_fMain);
InsertNode(nInsertAt, dnNew);
CloseAtHelper(nInsertAt, nChild);
// Parent's parent shouldn't be the child document node
if (dnParent != null && dnParent.Parent == dnNew)
{
Invariant.Assert(false, "Parent's Parent node shouldn't be the child node!");
}
// Patch the child count of the ancestors
dnNew.Parent = dnParent;
for (; dnParent != null; dnParent = dnParent.ClosedParent)
{
dnParent.ChildCount += 1;
}
AssertTreeInvariants();
}
internal void Excise(int nAt, int nExcise)
{
DocumentNode dn = EntryAt(nAt);
// Mark the nodes as deleted from main array
if (_fMain)
{
int nEnd = nAt + nExcise;
for (int i = nAt; i < nEnd; i++)
{
DocumentNode dn1 = EntryAt(i);
dn1.Index = -1;
dn1.DNA = null;
}
}
// Remove from the array.
RemoveRange(nAt, nExcise);
if (_fMain)
{
// Patch the child count of the ancestors
for (DocumentNode dnPa = dn.Parent; dnPa != null; dnPa = dnPa.Parent)
{
if (!dnPa.IsPending)
{
Debug.Assert(dnPa.LastChildIndex >= nAt + nExcise - 1);
dnPa.ChildCount = dnPa.ChildCount - nExcise;
}
}
// Patch the Index of trailing nodes
for (; nAt < Count; nAt++)
{
EntryAt(nAt).Index = nAt;
}
AssertTreeInvariants();
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal DocumentNode Top
{
get
{
return Count > 0 ? EntryAt(Count - 1) : null;
}
}
internal bool IsMain
{
set
{
_fMain = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
// <Summary>
// The PreCoalesce process for a ListItem involves seeing if I can migrate the left indent
// from contained paragraphs to the ListItem itself. This results in better bullet placement
// in the generated XAML.
// </Summary>
private void PreCoalesceListItem(DocumentNode dn)
{
int nAt = dn.Index;
long nMargin = -1;
int nEndItem = nAt + dn.ChildCount;
for (int nnAt = nAt + 1; nnAt <= nEndItem; nnAt++)
{
DocumentNode ddn = EntryAt(nnAt);
if (ddn.Type == DocumentNodeType.dnParagraph)
{
if (nMargin == -1)
{
nMargin = ddn.NearMargin;
}
else if (ddn.NearMargin < nMargin && ddn.IsNonEmpty)
{
nMargin = ddn.NearMargin;
}
}
}
dn.NearMargin = nMargin;
for (int nnAt = nAt; nnAt <= nEndItem; nnAt++)
{
DocumentNode ddn = EntryAt(nnAt);
if (ddn.Type == DocumentNodeType.dnParagraph)
{
ddn.NearMargin = ddn.NearMargin - nMargin;
}
}
}
// <Summary>
// The PreCoalesce process for a List involves promoting the flowdirection if at all possible
// from contained paragraphs to the list itself. This ensures that Avalon displays the list
// bullets in the proper location.
// </Summary>
private void PreCoalesceList(DocumentNode dn)
{
int nAt = dn.Index;
bool bConflict = false;
DirState ds = DirState.DirDefault;
int nEndItem = nAt + dn.ChildCount;
for (int nnAt = nAt + 1; !bConflict && nnAt <= nEndItem; nnAt++)
{
DocumentNode ddn = EntryAt(nnAt);
if (ddn.Type == DocumentNodeType.dnParagraph && ddn.IsNonEmpty)
{
if (ds == DirState.DirDefault)
{
ds = ddn.FormatState.DirPara;
}
else if (ds != ddn.FormatState.DirPara)
{
bConflict = true;
}
}
}
// OK, promote if possible.
if (!bConflict && ds != DirState.DirDefault)
{
for (int nnAt = nAt; nnAt <= nEndItem; nnAt++)
{
DocumentNode ddn = EntryAt(nnAt);
if (ddn.Type == DocumentNodeType.dnList || ddn.Type == DocumentNodeType.dnListItem)
{
ddn.FormatState.DirPara = ds;
}
}
}
}
// <Summary>
// Table column handling. RTF tables allow each row to be arbitrarily aligned. XAML (like HTML)
// doesn't allow that. You can achieve that effect in HTML by inserting extra rows with spurious
// cells propped to a specific width, but I'm not going to do that. Instead, I'm going to split
// the rows into separate tables when combining some set of rows into a table would force me
// to fabricate a column that doesn't contain any defined cell.
// </Summary>
private int PreCoalesceTable(DocumentNode dn)
{
int nInserted = 0;
int nAt = dn.Index;
ColumnStateArray cols = dn.ComputeColumns();
// OK, now I have a set of columns and information about which row caused the column to
// be instantiated. The naive algorithm is to strip the first N rows from the table until
// the row that caused an uninstantiated column, break the table there, and then run the
// algorithm again on the trailing table.
int nUnfilledRowIndex = cols.GetMinUnfilledRowIndex();
if (nUnfilledRowIndex > 0)
{
// OK, Need to insert a new table and table group around the remaining rows.
DocumentNode dnNewTable = new DocumentNode(DocumentNodeType.dnTable);
DocumentNode dnNewTableBody = new DocumentNode(DocumentNodeType.dnTableBody);
dnNewTable.FormatState = new FormatState(dn.FormatState);
dnNewTable.FormatState.RowFormat = EntryAt(nUnfilledRowIndex).FormatState.RowFormat;
int nChildrenOldTable = nUnfilledRowIndex - dn.Index - 1;
int nChildrenNewTable = dn.ChildCount - nChildrenOldTable;
dn.ChildCount = nChildrenOldTable; // Update old table child count
EntryAt(nAt + 1).ChildCount = nChildrenOldTable - 1; // Update old TableBody child count
InsertNode(nUnfilledRowIndex, dnNewTableBody);
CloseAtHelper(nUnfilledRowIndex, nChildrenNewTable);
InsertNode(nUnfilledRowIndex, dnNewTable);
CloseAtHelper(nUnfilledRowIndex, nChildrenNewTable + 1);
// Adjust parent pointers
dnNewTableBody.Parent = dnNewTable;
dnNewTable.Parent = dn.ClosedParent;
for (DocumentNode dnPa = dnNewTable.ClosedParent; dnPa != null; dnPa = dnPa.ClosedParent)
{
dnPa.ChildCount = dnPa.ChildCount + 2;
}
// Adjust the loop end to account for the newly inserted elements
nInserted = 2;
// Need to recompute the ColumnStateArray for the newly truncated table.
dn.ColumnStateArray = dn.ComputeColumns();
}
else
{
dn.ColumnStateArray = cols;
}
return nInserted;
}
private void PreCoalesceRow(DocumentNode dn, ref bool fVMerged)
{
DocumentNodeArray dnaCells = dn.GetRowsCells();
RowFormat rf = dn.FormatState.RowFormat;
DocumentNode dnTable = dn.GetParentOfType(DocumentNodeType.dnTable);
ColumnStateArray csa = (dnTable != null) ? dnTable.ColumnStateArray : null;
// Normally number of cells and cell definitions are equal, but be careful.
int nCount = dnaCells.Count < rf.CellCount ? dnaCells.Count : rf.CellCount;
// Non-unary colspan can arise both because I have "merged" cells specified
// as well as because I just have cells that exactly span some other cols.
// The code in PreCoalesce enforces that the cells line up, so I can just
// test for that here.
int nColsSeen = 0;
int i = 0;
while (i < nCount)
{
DocumentNode dnCell = dnaCells.EntryAt(i);
CellFormat cf = rf.NthCellFormat(i);
long cellx = cf.CellX;
// optimization - record if we encountered a vmerged cell
if (cf.IsVMerge)
{
fVMerged = true;
}
// Determine colspan based on cells we will eliminate through the merge flags
if (cf.IsHMergeFirst)
{
for (i++; i < nCount; i++)
{
cf = rf.NthCellFormat(i);
if (cf.IsVMerge)
{
fVMerged = true;
}
if (cf.IsHMerge)
{
dnaCells.EntryAt(i).ColSpan = 0; // zero means omit this cell
}
}
}
else
{
i++;
}
// Determine actual colspan based on cellx value
if (csa != null)
{
int nColStart = nColsSeen;
while (nColsSeen < csa.Count)
{
ColumnState cs = csa.EntryAt(nColsSeen);
nColsSeen++;
// This is the normal case
if (cs.CellX == cellx)
{
break;
}
// This is anomalous, but can occur with odd \cellx values (non-monotonically increasing).
if (cs.CellX > cellx)
{
break;
}
}
if (nColsSeen - nColStart > dnCell.ColSpan)
{
dnCell.ColSpan = nColsSeen - nColStart;
}
}
}
}
private void ProcessTableRowSpan(DocumentNodeArray dnaTables)
{
for (int i = 0; i < dnaTables.Count; i++)
{
DocumentNode dnTable = dnaTables.EntryAt(i);
ColumnStateArray csa = dnTable.ColumnStateArray;
if (csa == null || csa.Count == 0)
{
continue;
}
int nDim = csa.Count;
DocumentNodeArray dnaRows = dnTable.GetTableRows();
DocumentNodeArray dnaSpanCells = new DocumentNodeArray();
for (int k = 0; k < nDim; k++)
{
dnaSpanCells.Add(null);
}
for (int j = 0; j < dnaRows.Count; j++)
{
DocumentNode dnRow = dnaRows.EntryAt(j);
RowFormat rf = dnRow.FormatState.RowFormat;
DocumentNodeArray dnaCells = dnRow.GetRowsCells();
int nCount = nDim;
if (rf.CellCount < nCount)
{
nCount = rf.CellCount;
}
if (dnaCells.Count < nCount)
{
nCount = dnaCells.Count;
}
// Nominally, the index into dnaSpanCells, dnaCells and RowFormat.NthCellFormat
// should all be the same. But in some cases we have spanning cells that don't
// actually have an explicit cell associated with it (the span is implicit in the
// cellx/width values). I can detect this case by finding a colspan > 1 that is
// not then followed by a HMerged format. In this case, I need to apply a correction
// to my iteration, since the ColumnStateArray will have an entry for that field.
int kCSA = 0; // this might advance faster
for (int k = 0; k < nCount && kCSA < dnaSpanCells.Count; k++)
{
DocumentNode dnCell = dnaCells.EntryAt(k);
CellFormat cf = rf.NthCellFormat(k);
if (cf.IsVMerge)
{
DocumentNode dnSpanningCell = dnaSpanCells.EntryAt(kCSA);
if (dnSpanningCell != null)
{
dnSpanningCell.RowSpan = dnSpanningCell.RowSpan + 1;
}
kCSA += dnCell.ColSpan;
dnCell.ColSpan = 0;
}
else
{
if (cf.IsVMergeFirst)
{
dnCell.RowSpan = 1;
dnaSpanCells[kCSA] = dnCell;
}
else
{
dnaSpanCells[kCSA] = null;
}
for (int l = kCSA + 1; l < kCSA + dnCell.ColSpan; l++)
{
dnaSpanCells[l] = null;
}
kCSA += dnCell.ColSpan;
}
}
}
}
}
#endregion PrivateMethods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private bool _fMain;
private DocumentNodeArray _dnaOpen;
#endregion Private Fields
}
/// <summary>
/// ConverterState
/// </summary>
internal class ConverterState
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// ConverterState Constructor
/// </summary>
internal ConverterState()
{
_rtfFormatStack = new RtfFormatStack();
_documentNodeArray = new DocumentNodeArray();
_documentNodeArray.IsMain = true;
_fontTable = new FontTable();
_colorTable = new ColorTable();
_listTable = new ListTable();
_listOverrideTable = new ListOverrideTable();
_defaultFont = -1;
_defaultLang = -1;
_defaultLangFE = -1;
_bMarkerWhiteSpace = false;
_bMarkerPresent = false;
_border = null;
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal FormatState PreviousTopFormatState(int fromTop)
{
return _rtfFormatStack.PrevTop(fromTop);
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal RtfFormatStack RtfFormatStack
{
get
{
return _rtfFormatStack;
}
}
internal FontTable FontTable
{
get
{
return _fontTable;
}
}
internal ColorTable ColorTable
{
get
{
return _colorTable;
}
}
internal ListTable ListTable
{
get
{
return _listTable;
}
}
internal ListOverrideTable ListOverrideTable
{
get
{
return _listOverrideTable;
}
}
internal DocumentNodeArray DocumentNodeArray
{
get
{
return _documentNodeArray;
}
}
internal FormatState TopFormatState
{
get
{
return _rtfFormatStack.Top();
}
}
internal int CodePage
{
get
{
return _codePage;
}
set
{
_codePage = value;
}
}
internal long DefaultFont
{
get
{
return _defaultFont;
}
set
{
_defaultFont = value;
}
}
internal long DefaultLang
{
get
{
return _defaultLang;
}
set
{
_defaultLang = value;
}
}
internal long DefaultLangFE
{
get
{
return _defaultLangFE;
}
set
{
_defaultLangFE = value;
}
}
internal bool IsMarkerWhiteSpace
{
get
{
return _bMarkerWhiteSpace;
}
set
{
_bMarkerWhiteSpace = value;
}
}
internal bool IsMarkerPresent
{
get
{
return _bMarkerPresent;
}
set
{
_bMarkerPresent = value;
}
}
internal BorderFormat CurrentBorder
{
get
{
return _border;
}
set
{
_border = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private RtfFormatStack _rtfFormatStack;
private DocumentNodeArray _documentNodeArray;
private FontTable _fontTable;
private ColorTable _colorTable;
private ListTable _listTable;
private ListOverrideTable _listOverrideTable;
private long _defaultFont;
private long _defaultLang;
private long _defaultLangFE;
private int _codePage;
private bool _bMarkerWhiteSpace;
private bool _bMarkerPresent;
private BorderFormat _border;
#endregion Private Fields
}
/// <summary>
/// RtfToXamlReader
/// </summary>
internal class RtfToXamlReader
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// RtfToXamlReader Constructor
/// </summary>
internal RtfToXamlReader(string rtfString)
{
_rtfBytes = Encoding.Default.GetBytes(rtfString);
_bForceParagraph = false;
Initialize();
}
private void Initialize()
{
_lexer = new RtfToXamlLexer(_rtfBytes);
_converterState = new ConverterState();
_converterState.RtfFormatStack.Push();
_outerXamlBuilder = new StringBuilder();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// RtfToXamlError process
/// </summary>
internal RtfToXamlError Process()
{
RtfToXamlError rtfToXamlError = RtfToXamlError.None;
RtfToken token = new RtfToken();
bool findUnknownDestinationToken = false;
int nStartCount = _converterState.RtfFormatStack.Count;
while (rtfToXamlError == RtfToXamlError.None)
{
rtfToXamlError = _lexer.Next(token, _converterState.TopFormatState);
if (rtfToXamlError != RtfToXamlError.None)
{
break;
}
switch (token.Type)
{
case RtfTokenType.TokenGroupStart:
_converterState.RtfFormatStack.Push();
findUnknownDestinationToken = false;
break;
case RtfTokenType.TokenGroupEnd:
ProcessGroupEnd();
findUnknownDestinationToken = false;
break;
case RtfTokenType.TokenInvalid:
rtfToXamlError = RtfToXamlError.InvalidFormat;
break;
case RtfTokenType.TokenEOF:
// Handle any anomalous missing group ends.
while (_converterState.RtfFormatStack.Count > 2 && _converterState.RtfFormatStack.Count > nStartCount)
ProcessGroupEnd();
AppendDocument();
return RtfToXamlError.None;
case RtfTokenType.TokenDestination:
findUnknownDestinationToken = true;
break;
case RtfTokenType.TokenControl:
{
RtfControlWordInfo controlWordInfo = token.RtfControlWordInfo;
if (controlWordInfo != null && !findUnknownDestinationToken)
{
if ((controlWordInfo.Flags & RtfControls.RTK_DESTINATION) != 0)
{
findUnknownDestinationToken = true;
}
}
if (findUnknownDestinationToken)
{
// Ignore unknown control on the current field result destination.
// Otherwise, the field result content will be ignoreed by the unknown rtf destination.
if (controlWordInfo != null &&
controlWordInfo.Control == RtfControlWord.Ctrl_Unknown &&
_converterState.TopFormatState.RtfDestination == RtfDestination.DestFieldResult)
{
controlWordInfo = null;
}
else
{
_converterState.TopFormatState.RtfDestination = RtfDestination.DestUnknown;
}
findUnknownDestinationToken = false;
}
if (controlWordInfo != null)
{
HandleControl(token, controlWordInfo);
}
break;
}
case RtfTokenType.TokenText:
ProcessText(token);
break;
case RtfTokenType.TokenTextSymbol:
ProcessTextSymbol(token);
break;
case RtfTokenType.TokenNewline:
case RtfTokenType.TokenNullChar:
// Eaten
break;
case RtfTokenType.TokenPictureData:
ProcessImage(_converterState.TopFormatState);
break;
}
}
return rtfToXamlError;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Output Xaml string from converting of Rtf
/// </summary>
internal string Output
{
get
{
return _outerXamlBuilder.ToString();
}
}
internal bool ForceParagraph
{
get
{
return _bForceParagraph;
}
set
{
_bForceParagraph = value;
}
}
internal ConverterState ConverterState
{
get
{
return _converterState;
}
}
// WpfPayload package that containing the image for the specified Xaml
internal WpfPayload WpfPayload
{
set
{
_wpfPayload = value;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
internal bool TreeContainsBlock()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
for (int i = 0; i < dna.Count; i++)
{
DocumentNode documentNode = dna.EntryAt(i);
if (documentNode.Type == DocumentNodeType.dnParagraph ||
documentNode.Type == DocumentNodeType.dnList ||
documentNode.Type == DocumentNodeType.dnTable)
{
return true;
}
}
return false;
}
internal void AppendDocument()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
// Remove any trailing whitespace that wasn't explicitly terminated
while (dna.Count > 0)
{
DocumentNode dnLast = dna.EntryAt(dna.Count - 1);
if (dnLast.IsInline && dnLast.IsWhiteSpace)
{
dna.Excise(dna.Count - 1, 1);
}
else
{
break;
}
}
// If RTF ended with inline content and no \par, might need to force it.
if (ForceParagraph || TreeContainsBlock())
{
if (dna.Count > 0)
{
DocumentNode dnLast = dna.EntryAt(dna.Count - 1);
if (dnLast.IsInline)
{
FormatState formatState = _converterState.TopFormatState;
if (formatState != null)
{
HandlePara(null, formatState);
}
}
}
}
dna.CloseAll();
dna.CoalesceAll(_converterState);
// Search for the paragraph node
bool bBlock = ForceParagraph || TreeContainsBlock();
// Add the document header
if (bBlock)
{
_outerXamlBuilder.Append("<Section xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xml:space=\"preserve\" >\r\n");
}
else
{
_outerXamlBuilder.Append("<Span xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xml:space=\"preserve\">");
}
// Add the document content
for (int i = 0; i < dna.Count; i++)
{
DocumentNode documentNode = dna.EntryAt(i);
_outerXamlBuilder.Append(documentNode.Xaml);
}
// Add the document footer
if (bBlock)
{
_outerXamlBuilder.Append("</Section>");
}
else
{
_outerXamlBuilder.Append("</Span>");
}
}
// <summary>
// ProcessField is called when the \field group is closed. There should be content in this group with the
// field instruction and the field result. All of these distinct content areas are wrapped by
// dnField nodes. The RtfDestination in the FormatState of the DocumentNode specifies which field
// property.
// </summary>
internal void ProcessField()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnFieldBegin = null;
DocumentNode dnFieldEnd = null;
DocumentNode dnFieldInstBegin = null;
DocumentNode dnFieldInstEnd = null;
DocumentNode dnFieldResultBegin = null;
DocumentNode dnFieldResultEnd = null;
for (int i = dna.Count - 1; i >= 0 && dnFieldBegin == null; i--)
{
DocumentNode dn = dna.EntryAt(i);
if (dn.Type == DocumentNodeType.dnFieldBegin)
{
switch (dn.FormatState.RtfDestination)
{
case RtfDestination.DestFieldInstruction:
dnFieldInstBegin = dn;
break;
case RtfDestination.DestFieldResult:
dnFieldResultBegin = dn;
break;
case RtfDestination.DestField:
dnFieldBegin = dn;
break;
}
}
else if (dn.Type == DocumentNodeType.dnFieldEnd)
{
switch (dn.FormatState.RtfDestination)
{
case RtfDestination.DestFieldInstruction:
dnFieldInstEnd = dn;
break;
case RtfDestination.DestFieldResult:
dnFieldResultEnd = dn;
break;
case RtfDestination.DestField:
dnFieldEnd = dn;
break;
}
}
}
// This is anomalous - bad input?
if (dnFieldBegin == null || dnFieldEnd == null)
{
return;
}
// Get the text of the instruction to determine how to handle it.
DocumentNode dnInstruction = null;
string pictureUri = null;
if (dnFieldInstBegin != null && dnFieldInstEnd != null)
{
if (dnFieldInstEnd.Index > dnFieldInstBegin.Index + 1)
{
string instructionName = string.Empty;
// Get the field instruction from the each text node of the
// field instruction child
for (int fieldInstruction = dnFieldInstBegin.Index + 1;
fieldInstruction < dnFieldInstEnd.Index;
fieldInstruction++)
{
DocumentNode dnChild = dna.EntryAt(fieldInstruction);
if (dnChild.Type == DocumentNodeType.dnText)
{
instructionName += dnChild.Xaml;
}
}
if (instructionName.Length > 0 && instructionName[0] == ' ')
{
instructionName = instructionName.Substring(1);
}
// Processs the instruction as hyperlink or symbol
if (instructionName.IndexOf("HYPERLINK", StringComparison.Ordinal) == 0)
{
dnInstruction = ProcessHyperlinkField(instructionName);
}
else if (instructionName.IndexOf("SYMBOL", StringComparison.Ordinal) == 0)
{
dnInstruction = ProcessSymbolField(instructionName);
}
else if (instructionName.IndexOf("INCLUDEPICTURE", StringComparison.Ordinal) == 0)
{
pictureUri = GetIncludePictureUri(instructionName);
}
}
}
// Get rid of everything but field result contents
if (dnFieldInstBegin != null && dnFieldInstEnd != null)
{
int nInst = dnFieldInstEnd.Index - dnFieldInstBegin.Index + 1;
dna.Excise(dnFieldInstBegin.Index, nInst);
}
if (dnFieldResultBegin != null)
dna.Excise(dnFieldResultBegin.Index, 1);
if (dnFieldResultEnd != null)
dna.Excise(dnFieldResultEnd.Index, 1);
// Record the region that is going to be spanned by the new field node.
int nInsertAt = dnFieldBegin.Index;
int nChildCount = dnFieldEnd.Index - dnFieldBegin.Index - 1;
DocumentNode dnPa = dnFieldBegin.ClosedParent;
// Get rid of the field nodes themselves.
dna.Excise(dnFieldBegin.Index, 1);
dna.Excise(dnFieldEnd.Index, 1);
if (pictureUri != null && nChildCount != 0)
{
DocumentNode dnImage = dna.EntryAt(nInsertAt);
if (dnImage.Type == DocumentNodeType.dnImage)
{
// Replace the Image UriSource with the picture uri.
// IE copying rtf content has the bogus wmetafile so the image quality
// is very low. Now we replace image with the included picture uri to get
// the image directly from the specified Uri.
int uriSourceIndex = dnImage.Xaml.IndexOf("UriSource=", StringComparison.Ordinal);
int uriSourceEndIndex = dnImage.Xaml.IndexOf('\"', uriSourceIndex + 11);
string imageXaml = dnImage.Xaml.Substring(0, uriSourceIndex);
imageXaml += $"UriSource=\"{pictureUri}\"";
imageXaml += dnImage.Xaml.Substring(uriSourceEndIndex + 1);
dnImage.Xaml = imageXaml;
}
}
// Now insert the node around the results.
if (dnInstruction != null)
{
// Never insert an inline node around block content. For example, Word/HTML allows hyperlinks
// around any content, but in XAML hyperlinks are only inline.
bool bOK = true;
if (dnInstruction.IsInline)
{
for (int i = nInsertAt; bOK && i < nInsertAt + nChildCount; i++)
{
if (dna.EntryAt(i).IsBlock)
{
bOK = false;
}
}
}
if (bOK)
{
// Insert the instruction node if it is text(symbol) or has the child count for
// hyperlink or include picture
if (dnInstruction.Type == DocumentNodeType.dnText || nChildCount != 0)
{
dna.InsertChildAt(dnPa, dnInstruction, nInsertAt, nChildCount);
// No, don't coalesce since spanned content may need paragraph parent to determine props.
// dna.CoalesceChildren(_converterState, nInsertAt);
}
}
// If hyperlink, need to wrap each paragraph's contents, since XAML treats hyperlink as inline.
else if (dnInstruction.Type == DocumentNodeType.dnHyperlink)
{
dnInstruction.AppendXamlPrefix(_converterState);
for (int i = nInsertAt; i < nInsertAt + nChildCount; i++)
{
DocumentNode dn = dna.EntryAt(i);
if (dn.Type == DocumentNodeType.dnParagraph && !dn.IsTerminated)
{
Debug.Assert(dn.ChildCount == 0);
dn.Xaml = $"{dnInstruction.Xaml}{dn.Xaml}</Hyperlink>";
}
}
// If I have trailing text that is not yet in a para, I need to wrap that as well
int nInline = 0;
int nInlineAt = nInsertAt + nChildCount - 1;
for (; nInlineAt >= nInsertAt; nInlineAt--)
{
DocumentNode dn = dna.EntryAt(nInlineAt);
if (dn.IsInline)
{
nInline++;
}
else
{
break;
}
}
if (nInline > 0)
{
WrapInlineInParagraph(nInlineAt + 1, nInline);
DocumentNode dn = dna.EntryAt(nInlineAt + 1);
if (dn.Type == DocumentNodeType.dnParagraph && !dn.IsTerminated)
{
Debug.Assert(dn.ChildCount == 0);
dn.Xaml = $"{dnInstruction.Xaml}{dn.Xaml}</Hyperlink>";
}
}
}
}
// If I processed a field result with block content, it's possible I left some remaining
// inline nodes unwrapped in a paragraph before the field. Let's look for them and clean them up.
bool bBlock = false;
for (int i = nInsertAt; !bBlock && i < dna.Count; i++)
{
DocumentNode dn = dna.EntryAt(i);
bBlock = dn.IsBlock;
}
if (bBlock)
{
int nVisible = 0;
nChildCount = 0;
for (int i = nInsertAt - 1; i >= 0; i--)
{
DocumentNode dn = dna.EntryAt(i);
if (dn.IsInline || dn.IsHidden)
{
nChildCount++;
if (!dn.IsHidden)
{
nVisible++;
}
}
if (dn.IsBlock)
{
nChildCount -= dn.ChildCount;
break;
}
}
if (nVisible > 0)
{
WrapInlineInParagraph(nInsertAt - nChildCount, nChildCount);
}
}
}
private int HexToInt(char c)
{
if (c >= '0' && c <= '9')
{
return (c - '0');
}
else if (c >= 'a' && c <= 'f')
{
return (10 + c - 'a');
}
else if (c >= 'A' && c <= 'F')
{
return (10 + c - 'A');
}
else
{
return 0;
}
}
private int DecToInt(char c)
{
if (c >= '0' && c <= '9')
{
return (c - '0');
}
else
{
return 0;
}
}
// Return the included picture Uri
private string GetIncludePictureUri(string instructionName)
{
string pictureUri = null;
int uriIndex = instructionName.IndexOf("http:", StringComparison.OrdinalIgnoreCase);
if (uriIndex != -1)
{
pictureUri = instructionName.Substring(uriIndex, instructionName.Length - uriIndex - 1);
int pictureUriEndIndex = pictureUri.IndexOf('\"');
if (pictureUriEndIndex != -1)
{
pictureUri = pictureUri.Substring(0, pictureUriEndIndex);
}
if (!Uri.IsWellFormedUriString(pictureUri, UriKind.Absolute))
{
pictureUri = null;
}
}
return pictureUri;
}
internal DocumentNode ProcessHyperlinkField(string instr)
{
DocumentNode dn = new DocumentNode(DocumentNodeType.dnHyperlink);
dn.FormatState = new FormatState(_converterState.PreviousTopFormatState(0));
string sUri = null;
string sTarget = null;
string sBookmark = null;
bool bTargetNext = false;
bool bBookmarkNext = false;
// iterate, starting past " HYPERLINK"
int i = 10;
while (i < instr.Length)
{
// Skip spaces
if (instr[i] == ' ')
{
i++;
}
// NavigateUri?
else if (instr[i] == '"')
{
i++;
if (i < instr.Length)
{
int iStart = i;
int iEnd = i;
for (; iEnd < instr.Length; iEnd++)
{
if (instr[iEnd] == '"')
{
break;
}
}
string param = instr.Substring(iStart, iEnd - iStart);
if (bTargetNext)
{
sTarget = param;
bTargetNext = false;
}
else if (bBookmarkNext)
{
sBookmark = param;
bBookmarkNext = false;
}
else if (sUri == null)
sUri = param;
// else eat the string
i = iEnd + 1;
}
}
// Instructions
else if (instr[i] == '\\')
{
i++;
if (i < instr.Length)
{
switch (instr[i])
{
case 'l': // bookmark
bBookmarkNext = true; bTargetNext = false;
break;
case 't': // target
bBookmarkNext = false; bTargetNext = true;
break;
}
i++;
}
}
// Ignore other characters
else
i++;
}
StringBuilder sb = new StringBuilder();
if (sUri != null)
{
sb.Append(sUri);
}
if (sBookmark != null)
{
sb.Append('#');
sb.Append(sBookmark);
}
// Remove the second backslash(which rtf specified) to keep only single backslash
for (int uriIndex = 0; uriIndex < sb.Length; uriIndex++)
{
if (sb[uriIndex] == '\\' && uriIndex + 1 < sb.Length && sb[uriIndex + 1] == '\\')
{
// Remove the sceond backslash
sb.Remove(uriIndex + 1, 1);
}
}
dn.NavigateUri = sb.ToString();
return dn.NavigateUri.Length > 0 ? dn : null;
}
internal DocumentNode ProcessSymbolField(string instr)
{
DocumentNode dn = new DocumentNode(DocumentNodeType.dnText);
dn.FormatState = new FormatState(_converterState.PreviousTopFormatState(0));
int nChar = -1;
EncodeType encodeType = EncodeType.Ansi;
// iterate, starting past " SYMBOL"
int i = 7;
while (i < instr.Length)
{
// Skip spaces
if (instr[i] == ' ')
{
i++;
}
// Is this the character code?
else if (nChar == -1 && instr[i] >= '0' && instr[i] <= '9')
{
// Hex or decimal?
if (instr[i] == '0' && i < instr.Length - 1 && instr[i + 1] == 'x')
{
i += 2;
nChar = 0;
for (; i < instr.Length && instr[i] != ' ' && instr[i] != '\\'; i++)
{
if (nChar < 0xFFFF)
{
nChar = (nChar * 16) + HexToInt(instr[i]);
}
}
}
else
{
nChar = 0;
for (; i < instr.Length && instr[i] != ' ' && instr[i] != '\\'; i++)
{
if (nChar < 0xFFFF)
{
nChar = (nChar * 10) + DecToInt(instr[i]);
}
}
}
}
// Instructions
else if (instr[i] == '\\')
{
i++;
if (i < instr.Length)
{
ProcessSymbolFieldInstruction(dn, instr, ref i, ref encodeType);
}
}
else
{
i++;
}
}
// If no character code was specified, just bail
if (nChar == -1)
{
return null;
}
// Otherwise, convert it to text
ConvertSymbolCharValueToText(dn, nChar, encodeType);
return (dn.Xaml != null && dn.Xaml.Length > 0) ? dn : null;
}
private void ProcessSymbolFieldInstruction(DocumentNode dn, string instr, ref int i, ref EncodeType encodeType)
{
int iStart = 0;
switch (instr[i++])
{
case 'a':
encodeType = EncodeType.Ansi;
break;
case 'u':
encodeType = EncodeType.Unicode;
break;
case 'j':
encodeType = EncodeType.ShiftJis;
break;
case 'h':
// linespacing instruction: ignore
break;
case 's':
if (i < instr.Length && instr[i] == ' ')
i++;
// font size in points, not half-points
iStart = i;
for (; i < instr.Length && instr[i] != ' '; i++)
{
continue;
}
ReadOnlySpan<char> ptString = instr.AsSpan(iStart, i - iStart);
// Now convert number part
bool ret = true;
double d = 0f;
try
{
d = double.Parse(ptString, provider: CultureInfo.InvariantCulture);
}
catch (System.OverflowException)
{
ret = false;
}
catch (System.FormatException)
{
ret = false;
}
if (ret)
{
dn.FormatState.FontSize = (long)((d * 2) + 0.5);
}
break;
case 'f':
// Font Name
if (i < instr.Length && instr[i] == ' ')
{
i++;
}
if (i < instr.Length && instr[i] == '"')
{
i++;
}
iStart = i;
for (; i < instr.Length && instr[i] != '"'; i++)
{
continue;
}
string name = instr.Substring(iStart, i - iStart);
// Move past trailing double-quote
i++;
if (name != null && name.Length > 0)
{
dn.FormatState.Font = _converterState.FontTable.DefineEntryByName(name);
}
break;
}
}
// Process image that create image node for generating Xaml Image control
// Disable inlining to avoid loading System.Drawing.Bitmap without need
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
internal void ProcessImage(FormatState formatState)
{
string contentType;
string imagePartUriString;
switch (formatState.ImageFormat)
{
case RtfImageFormat.Wmf:
case RtfImageFormat.Png:
contentType = "image/png";
break;
case RtfImageFormat.Jpeg:
contentType = "image/jpeg";
break;
default:
contentType = string.Empty;
break;
}
bool skipImage = (formatState.ImageScaleWidth < 0) || (formatState.ImageScaleHeight < 0);
if (_wpfPayload != null && contentType != string.Empty && !skipImage)
{
// Get image part URI string and image binary steam to write Rtf image data
// into the container of WpfPayload
Stream imageStream = _wpfPayload.CreateImageStream(_imageCount, contentType, out imagePartUriString);
using (imageStream)
{
if (formatState.ImageFormat != RtfImageFormat.Wmf)
{
// Write the image binary data on the container from Rtf image data
_lexer.WriteImageData(imageStream, formatState.IsImageDataBinary);
}
else
{
// Read the windows metafile from the rtf content and then convert it
// to bitmap data then save it as PNG on the container image part
MemoryStream metafileStream = new MemoryStream();
using (metafileStream)
{
// Get Windows Metafile from rtf content
_lexer.WriteImageData(metafileStream, formatState.IsImageDataBinary);
metafileStream.Position = 0;
SystemDrawingHelper.SaveMetafileToImageStream(metafileStream, imageStream);
}
}
}
// Increase the image count to generate the image source name
_imageCount++;
formatState.ImageSource = imagePartUriString;
// Create the image document node
DocumentNode dnImage = new DocumentNode(DocumentNodeType.dnImage);
dnImage.FormatState = formatState;
StringBuilder imageStringBuilder = new StringBuilder();
// Add the xaml image element
imageStringBuilder.Append("<Image ");
// Add the xaml image width property
imageStringBuilder.Append(" Width=\"");
double width;
if (formatState.ImageScaleWidth != 0)
{
width = formatState.ImageWidth * (formatState.ImageScaleWidth / 100);
}
else
{
width = formatState.ImageWidth;
}
imageStringBuilder.Append(width.ToString(CultureInfo.InvariantCulture));
imageStringBuilder.Append('"');
// Add the xaml image height property
imageStringBuilder.Append(" Height=\"");
double height = formatState.ImageHeight * (formatState.ImageScaleHeight / 100);
if (formatState.ImageScaleHeight != 0)
{
height = formatState.ImageHeight * (formatState.ImageScaleHeight / 100);
}
else
{
height = formatState.ImageHeight;
}
imageStringBuilder.Append(height.ToString(CultureInfo.InvariantCulture));
imageStringBuilder.Append('"');
// Add the xaml image baseline offset property
if (formatState.IncludeImageBaselineOffset == true)
{
double baselineOffset = height - formatState.ImageBaselineOffset;
imageStringBuilder.Append(" TextBlock.BaselineOffset=\"");
imageStringBuilder.Append(baselineOffset.ToString(CultureInfo.InvariantCulture));
imageStringBuilder.Append('"');
}
// Add the xaml image stretch property
imageStringBuilder.Append(" Stretch=\"Fill");
imageStringBuilder.Append('"');
// Add the xaml image close tag
imageStringBuilder.Append('>');
// Add the image source property as the complex property
// This is for specifying BitmapImage.CacheOption as OnLoad instead of
// the default OnDemand option.
imageStringBuilder.Append("<Image.Source>");
imageStringBuilder.Append("<BitmapImage ");
imageStringBuilder.Append("UriSource=\"");
imageStringBuilder.Append(imagePartUriString);
imageStringBuilder.Append("\" ");
imageStringBuilder.Append("CacheOption=\"OnLoad\" ");
imageStringBuilder.Append("/>");
imageStringBuilder.Append("</Image.Source>");
imageStringBuilder.Append("</Image>");
// Set Xaml for image element
dnImage.Xaml = imageStringBuilder.ToString();
// Insert the image document node to the document node array
DocumentNodeArray dna = _converterState.DocumentNodeArray;
dna.Push(dnImage);
dna.CloseAt(dna.Count - 1);
}
else
{
// Skip the image data if the image type is unknown or WpfPayload is null
_lexer.AdvanceForImageData();
}
}
private void ConvertSymbolCharValueToText(DocumentNode dn, int nChar, EncodeType encodeType)
{
switch (encodeType)
{
case EncodeType.Unicode:
if (nChar < 0xFFFF)
{
dn.AppendXamlEncoded(char.ToString((char)nChar));
}
break;
case EncodeType.ShiftJis:
if (nChar < 0xFFFF)
{
// NB: How to interpret this numeric value as Shift-JIS?
Encoding ec = InternalEncoding.GetEncoding(932);
int nChars = nChar > 256 ? 2 : 1;
byte[] ba = new byte[2];
if (nChars == 1)
{
ba[0] = (byte)nChar;
}
else
{
ba[0] = (byte)((nChar >> 8) & 0xFF);
ba[1] = (byte)(nChar & 0xFF);
}
dn.AppendXamlEncoded(ec.GetString(ba, 0, nChars));
}
break;
default:
if (nChar < 256)
{
// Keep the byte char value as the unicode
char singleChar = (char) nChar;
dn.AppendXamlEncoded(new string(singleChar, 1));
}
break;
}
}
internal void ProcessListText()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
int ndnListText = dna.FindPending(DocumentNodeType.dnListText);
if (ndnListText >= 0)
{
DocumentNode dnListText = dna.EntryAt(ndnListText);
dna.CloseAt(ndnListText);
// If I didn't throw away a picture, need to actually test for content.
bool bWS = true;
if (dnListText.HasMarkerContent)
{
bWS = false;
}
else
{
int nEnd = ndnListText + dnListText.ChildCount;
for (int i = ndnListText + 1; bWS && i <= nEnd; i++)
{
DocumentNode dnText = dna.EntryAt(i);
if (!dnText.IsWhiteSpace)
{
bWS = false;
}
}
}
dna.CoalesceChildren(_converterState, ndnListText);
if (bWS)
{
_converterState.IsMarkerWhiteSpace = true;
}
dnListText.Xaml = string.Empty;
_converterState.IsMarkerPresent = true;
}
}
internal void ProcessShapeResult()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
int nAt = dna.FindPending(DocumentNodeType.dnShape);
if (nAt >= 0)
{
// Make sure any pending inlines are wrapped.
FormatState formatState = _converterState.TopFormatState;
if (formatState != null)
{
WrapPendingInlineInParagraph(null, formatState);
}
// No structures should leak out of a shape result
dna.CloseAt(nAt);
}
}
private void ProcessRtfDestination(FormatState fsCur)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
int nAt;
switch (fsCur.RtfDestination)
{
case RtfDestination.DestField:
nAt = dna.FindUnmatched(DocumentNodeType.dnFieldBegin);
if (nAt >= 0)
{
DocumentNode dnEnd = new DocumentNode(DocumentNodeType.dnFieldEnd);
dnEnd.FormatState = new FormatState(fsCur);
dnEnd.IsPending = false;
dna.Push(dnEnd);
dna.EntryAt(nAt).IsMatched = true;
ProcessField();
}
break;
case RtfDestination.DestFieldInstruction:
case RtfDestination.DestFieldPrivate:
case RtfDestination.DestFieldResult:
nAt = dna.FindUnmatched(DocumentNodeType.dnFieldBegin);
if (nAt >= 0)
{
DocumentNode dnEnd = new DocumentNode(DocumentNodeType.dnFieldEnd);
dnEnd.FormatState = new FormatState(fsCur);
dnEnd.IsPending = false;
dna.Push(dnEnd);
dna.EntryAt(nAt).IsMatched = true;
}
break;
// The DestShape destination is only to distinguish the case of leaving a shaperesult destination
// when shapes were nested. No processing is actually necessary here.
case RtfDestination.DestShape:
break;
case RtfDestination.DestShapeResult:
ProcessShapeResult();
break;
case RtfDestination.DestListText:
ProcessListText();
break;
case RtfDestination.DestListLevel:
{
ListTableEntry listTableEntry = _converterState.ListTable.CurrentEntry;
if (listTableEntry != null)
{
ListLevel listLevel = listTableEntry.Levels.CurrentEntry;
listLevel.FormatState = new FormatState(fsCur);
}
}
break;
case RtfDestination.DestListOverride:
break;
case RtfDestination.DestList:
break;
case RtfDestination.DestFontName:
FontTableEntry entry = _converterState.FontTable.CurrentEntry;
if (entry != null)
{
entry.IsNameSealed = true;
entry.IsPending = false;
}
break;
case RtfDestination.DestFontTable:
_converterState.FontTable.MapFonts();
break;
}
}
internal void ProcessGroupEnd()
{
// Pop the state for this group. Don't overpop if we are handed bad input
if (_converterState.RtfFormatStack.Count > 2)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsNew = _converterState.PreviousTopFormatState(1);
// Various interesting actions are taken when the destination changes
if (fsCur.RtfDestination != fsNew.RtfDestination)
{
ProcessRtfDestination(fsCur);
}
else if (fsNew.RtfDestination == RtfDestination.DestFontTable)
{
FontTableEntry entry = _converterState.FontTable.CurrentEntry;
if (entry != null)
{
entry.IsPending = false;
}
}
_converterState.RtfFormatStack.Pop();
if (fsNew.CodePage == -1)
{
_lexer.CodePage = _converterState.CodePage;
}
else
{
_lexer.CodePage = fsNew.CodePage;
}
// After seeing font table, setup default font for current state.
if (fsCur.RtfDestination == RtfDestination.DestFontTable
&& _converterState.DefaultFont >= 0)
{
SelectFont(_converterState.DefaultFont);
}
// If we ever pop to a state without a default font, re-assert it.
else if (fsCur.Font < 0 && _converterState.DefaultFont >= 0)
{
SelectFont(_converterState.DefaultFont);
}
}
}
internal void SelectFont(long nFont)
{
FormatState formatState = _converterState.TopFormatState;
if (formatState == null)
{
return;
}
formatState.Font = nFont;
FontTableEntry entry = _converterState.FontTable.FindEntryByIndex((int)formatState.Font);
if (entry != null)
{
if (entry.CodePage == -1)
{
formatState.CodePage = _converterState.CodePage;
}
else
{
formatState.CodePage = entry.CodePage;
}
_lexer.CodePage = formatState.CodePage;
}
}
internal void HandleControl(RtfToken token, RtfControlWordInfo controlWordInfo)
{
FormatState formatState = _converterState.TopFormatState;
if (formatState == null)
{
return;
}
switch (controlWordInfo.Control)
{
case RtfControlWord.Ctrl_V:
formatState.IsHidden = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_DELETED:
formatState.IsHidden = true;
break;
case RtfControlWord.Ctrl_B:
formatState.Bold = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_I:
formatState.Italic = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_IMPR:
formatState.Engrave = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_OUTL:
formatState.Outline = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_SHAD:
formatState.Shadow = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_SCAPS:
formatState.SCaps = token.ToggleValue > 0;
break;
case RtfControlWord.Ctrl_FS:
if (Validators.IsValidFontSize(token.Parameter))
formatState.FontSize = token.Parameter;
break;
case RtfControlWord.Ctrl_EXPND:
case RtfControlWord.Ctrl_EXPNDTW:
formatState.Expand = token.Parameter;
break;
case RtfControlWord.Ctrl_F:
if (token.HasParameter)
if (formatState.RtfDestination == RtfDestination.DestFontTable)
{
_converterState.FontTable.DefineEntry((int)token.Parameter);
}
else
{
SelectFont(token.Parameter);
// Setting font also sets current language in effect
if (formatState.FontSlot == FontSlot.DBCH)
{
if (formatState.LangFE > 0)
{
formatState.LangCur = formatState.LangFE;
}
else if (_converterState.DefaultLangFE > 0)
{
formatState.LangCur = _converterState.DefaultLangFE;
}
}
else
{
if (formatState.Lang > 0)
{
formatState.LangCur = formatState.Lang;
}
else if (_converterState.DefaultLang > 0)
{
formatState.LangCur = _converterState.DefaultLang;
}
}
}
break;
case RtfControlWord.Ctrl_DBCH:
formatState.FontSlot = FontSlot.DBCH;
break;
case RtfControlWord.Ctrl_LOCH:
formatState.FontSlot = FontSlot.LOCH;
break;
case RtfControlWord.Ctrl_HICH:
formatState.FontSlot = FontSlot.HICH;
break;
case RtfControlWord.Ctrl_LANG:
formatState.Lang = token.Parameter;
formatState.LangCur = token.Parameter;
break;
case RtfControlWord.Ctrl_LANGFE:
formatState.LangFE = token.Parameter;
break;
case RtfControlWord.Ctrl_DEFLANG:
_converterState.DefaultLang = token.Parameter;
break;
case RtfControlWord.Ctrl_DEFLANGFE:
_converterState.DefaultLangFE = token.Parameter;
break;
case RtfControlWord.Ctrl_DEFF:
_converterState.DefaultFont = token.Parameter;
break;
case RtfControlWord.Ctrl_FNAME:
{
FormatState previousFormatState = _converterState.PreviousTopFormatState(1);
if (previousFormatState.RtfDestination == RtfDestination.DestFontTable)
{
formatState.RtfDestination = RtfDestination.DestFontName;
FontTableEntry entry = _converterState.FontTable.CurrentEntry;
if (entry != null)
{
entry.Name = null;
}
}
}
break;
case RtfControlWord.Ctrl_FCHARSET:
if (formatState.RtfDestination == RtfDestination.DestFontTable)
{
HandleFontTableTokens(token);
}
break;
case RtfControlWord.Ctrl_HIGHLIGHT:
formatState.CB = token.Parameter;
break;
case RtfControlWord.Ctrl_CB:
formatState.CB = token.Parameter;
break;
case RtfControlWord.Ctrl_CF:
formatState.CF = token.Parameter;
break;
case RtfControlWord.Ctrl_SUB:
formatState.Sub = token.FlagValue;
if (formatState.Sub)
{
formatState.Super = false;
}
break;
case RtfControlWord.Ctrl_SUPER:
formatState.Super = token.FlagValue;
if (formatState.Super)
{
formatState.Sub = false;
}
break;
case RtfControlWord.Ctrl_NOSUPERSUB:
formatState.Sub = false;
formatState.Super = false;
formatState.SuperOffset = 0;
break;
case RtfControlWord.Ctrl_UP:
formatState.SuperOffset = token.Parameter;
break;
case RtfControlWord.Ctrl_ULC:
// NB: underline color not implemented
break;
case RtfControlWord.Ctrl_UL:
case RtfControlWord.Ctrl_ULD:
case RtfControlWord.Ctrl_ULDASH:
case RtfControlWord.Ctrl_ULDASHD:
case RtfControlWord.Ctrl_ULDASHDD:
case RtfControlWord.Ctrl_ULDB:
case RtfControlWord.Ctrl_ULHAIR:
case RtfControlWord.Ctrl_ULHWAVE:
case RtfControlWord.Ctrl_ULLDASH:
case RtfControlWord.Ctrl_ULTH:
case RtfControlWord.Ctrl_ULTHD:
case RtfControlWord.Ctrl_ULTHDASH:
case RtfControlWord.Ctrl_ULTHDASHD:
case RtfControlWord.Ctrl_ULTHDASHDD:
case RtfControlWord.Ctrl_ULTHLDASH:
case RtfControlWord.Ctrl_ULULDBWAVE:
case RtfControlWord.Ctrl_ULWAVE:
case RtfControlWord.Ctrl_ULW:
formatState.UL = (token.ToggleValue > 0 ? ULState.ULNormal : ULState.ULNone);
break;
case RtfControlWord.Ctrl_ULNONE:
formatState.UL = ULState.ULNone;
break;
case RtfControlWord.Ctrl_STRIKE:
formatState.Strike = token.ToggleValue > 0 ? StrikeState.StrikeNormal : StrikeState.StrikeNone;
break;
case RtfControlWord.Ctrl_STRIKED:
formatState.Strike = token.ToggleValue > 0 ? StrikeState.StrikeDouble : StrikeState.StrikeNone;
break;
case RtfControlWord.Ctrl_PLAIN:
formatState.SetCharDefaults();
if (_converterState.DefaultFont >= 0)
{
SelectFont(_converterState.DefaultFont);
}
break;
// Paragraph settings
case RtfControlWord.Ctrl_PARD:
formatState.SetParaDefaults();
break;
case RtfControlWord.Ctrl_SB:
formatState.SB = token.Parameter;
break;
case RtfControlWord.Ctrl_SA:
formatState.SA = token.Parameter;
break;
case RtfControlWord.Ctrl_FI:
// Word first indent(\fiN) and Avalon TextIndent concept is the different.
// So we ignore the negative first indent safely which can make the content invisible.
// Word: \fi-200 -200 0 200
// abc
// xyz
// Word: \fi200 -200 0 200
// abc
// xyz
if (token.Parameter > 0)
{
formatState.FI = token.Parameter;
}
break;
case RtfControlWord.Ctrl_LIN:
// We ignore \lin and do RTL fixup on margins outselves.
// formatState.LI = token.Parameter;
break;
case RtfControlWord.Ctrl_LI:
formatState.LI = token.Parameter;
break;
case RtfControlWord.Ctrl_RIN:
// We ignore \rin and do RTL fixup on margins outselves.
// formatState.RI = token.Parameter;
break;
case RtfControlWord.Ctrl_RI:
formatState.RI = token.Parameter;
break;
case RtfControlWord.Ctrl_QC:
formatState.HAlign = HAlign.AlignCenter;
break;
case RtfControlWord.Ctrl_QJ:
formatState.HAlign = HAlign.AlignJustify;
break;
case RtfControlWord.Ctrl_QL:
formatState.HAlign = HAlign.AlignLeft;
break;
case RtfControlWord.Ctrl_QR:
formatState.HAlign = HAlign.AlignRight;
break;
case RtfControlWord.Ctrl_CFPAT:
formatState.CFPara = token.Parameter;
break;
case RtfControlWord.Ctrl_SHADING:
// Shading of the CFPAT color, in 100's of a percent.
formatState.ParaShading = token.Parameter;
break;
case RtfControlWord.Ctrl_CBPAT:
formatState.CBPara = token.Parameter;
break;
case RtfControlWord.Ctrl_SL:
formatState.SL = token.Parameter;
break;
case RtfControlWord.Ctrl_SLMULT:
formatState.SLMult = token.ToggleValue > 0;
break;
// Special text
case RtfControlWord.Ctrl_LINE:
ProcessHardLine(token, formatState);
break;
// LTR-RTL stuff
case RtfControlWord.Ctrl_LTRCH:
formatState.DirChar = DirState.DirLTR;
break;
case RtfControlWord.Ctrl_LTRPAR:
case RtfControlWord.Ctrl_LTRDOC:
case RtfControlWord.Ctrl_LTRSECT:
formatState.DirPara = DirState.DirLTR;
break;
case RtfControlWord.Ctrl_RTLCH:
formatState.DirChar = DirState.DirRTL;
break;
case RtfControlWord.Ctrl_RTLPAR:
case RtfControlWord.Ctrl_RTLDOC:
case RtfControlWord.Ctrl_RTLSECT:
formatState.DirPara = DirState.DirRTL;
// RTF defaults paragraph alignment to left for both RTL and LTR paragraphs, but
// Avalon switches the default. Hard-code the alignment here when dealing with RTL
// paragraphs so that the distinction doesn't result in different defaults.
if (formatState.HAlign == HAlign.AlignDefault)
{
formatState.HAlign = HAlign.AlignLeft;
}
break;
case RtfControlWord.Ctrl_LTRMARK:
ProcessText(new string('\x200e', 1));
break;
case RtfControlWord.Ctrl_RTLMARK:
ProcessText(new string('\x200f', 1));
break;
// Structure output
case RtfControlWord.Ctrl_PAR:
case RtfControlWord.Ctrl_SECT:
HandlePara(token, formatState);
break;
case RtfControlWord.Ctrl_PAGE:
HandlePage(token, formatState);
break;
// Table Tokens
case RtfControlWord.Ctrl_CELL:
case RtfControlWord.Ctrl_NESTCELL:
case RtfControlWord.Ctrl_ROW:
case RtfControlWord.Ctrl_TROWD:
case RtfControlWord.Ctrl_NESTROW:
case RtfControlWord.Ctrl_NESTTABLEPROPS:
HandleTableTokens(token, formatState);
break;
// Table property tokens
case RtfControlWord.Ctrl_TRGAPH:
case RtfControlWord.Ctrl_TRLEFT:
case RtfControlWord.Ctrl_TRQC:
case RtfControlWord.Ctrl_TRQL:
case RtfControlWord.Ctrl_TRQR:
case RtfControlWord.Ctrl_TRPADDL:
case RtfControlWord.Ctrl_TRPADDR:
case RtfControlWord.Ctrl_TRPADDB:
case RtfControlWord.Ctrl_TRPADDT:
case RtfControlWord.Ctrl_TRPADDFL:
case RtfControlWord.Ctrl_TRPADDFT:
case RtfControlWord.Ctrl_TRPADDFB:
case RtfControlWord.Ctrl_TRPADDFR:
case RtfControlWord.Ctrl_TRFTSWIDTH:
case RtfControlWord.Ctrl_TRFTSWIDTHB:
case RtfControlWord.Ctrl_TRFTSWIDTHA:
case RtfControlWord.Ctrl_TRWWIDTH:
case RtfControlWord.Ctrl_TRWWIDTHB:
case RtfControlWord.Ctrl_TRWWIDTHA:
case RtfControlWord.Ctrl_TRAUTOFIT:
case RtfControlWord.Ctrl_CLWWIDTH:
case RtfControlWord.Ctrl_CLFTSWIDTH:
case RtfControlWord.Ctrl_TRBRDRT:
case RtfControlWord.Ctrl_TRBRDRB:
case RtfControlWord.Ctrl_TRBRDRR:
case RtfControlWord.Ctrl_TRBRDRL:
case RtfControlWord.Ctrl_TRBRDRV:
case RtfControlWord.Ctrl_TRBRDRH:
case RtfControlWord.Ctrl_CLVERTALT:
case RtfControlWord.Ctrl_CLVERTALB:
case RtfControlWord.Ctrl_CLVERTALC:
case RtfControlWord.Ctrl_CLSHDRAWNIL:
case RtfControlWord.Ctrl_CLSHDNG:
case RtfControlWord.Ctrl_CLBRDRB:
case RtfControlWord.Ctrl_CLBRDRR:
case RtfControlWord.Ctrl_CLBRDRT:
case RtfControlWord.Ctrl_CLBRDRL:
case RtfControlWord.Ctrl_CLCBPAT:
case RtfControlWord.Ctrl_CLCFPAT:
case RtfControlWord.Ctrl_CLPADL:
case RtfControlWord.Ctrl_CLPADFL:
case RtfControlWord.Ctrl_CLPADR:
case RtfControlWord.Ctrl_CLPADFR:
case RtfControlWord.Ctrl_CLPADT:
case RtfControlWord.Ctrl_CLPADFT:
case RtfControlWord.Ctrl_CLPADB:
case RtfControlWord.Ctrl_CLPADFB:
case RtfControlWord.Ctrl_CELLX:
case RtfControlWord.Ctrl_BRDRART:
case RtfControlWord.Ctrl_BRDRB:
case RtfControlWord.Ctrl_BRDRNIL:
case RtfControlWord.Ctrl_BRDRNONE:
case RtfControlWord.Ctrl_BRDRTBL:
case RtfControlWord.Ctrl_BRDRBAR:
case RtfControlWord.Ctrl_BRDRBTW:
case RtfControlWord.Ctrl_BRDRCF:
case RtfControlWord.Ctrl_BRDRDASH:
case RtfControlWord.Ctrl_BRDRDASHD:
case RtfControlWord.Ctrl_BRDRDASHDD:
case RtfControlWord.Ctrl_BRDRDASHDOTSTR:
case RtfControlWord.Ctrl_BRDRDASHSM:
case RtfControlWord.Ctrl_BRDRDB:
case RtfControlWord.Ctrl_BRDRDOT:
case RtfControlWord.Ctrl_BRDREMBOSS:
case RtfControlWord.Ctrl_BRDRENGRAVE:
case RtfControlWord.Ctrl_BRDRFRAME:
case RtfControlWord.Ctrl_BRDRHAIR:
case RtfControlWord.Ctrl_BRDRINSET:
case RtfControlWord.Ctrl_BRDRL:
case RtfControlWord.Ctrl_BRDROUTSET:
case RtfControlWord.Ctrl_BRDRR:
case RtfControlWord.Ctrl_BRDRS:
case RtfControlWord.Ctrl_BRDRSH:
case RtfControlWord.Ctrl_BRDRT:
case RtfControlWord.Ctrl_BRDRTH:
case RtfControlWord.Ctrl_BRDRTHTNLG:
case RtfControlWord.Ctrl_BRDRTHTNMG:
case RtfControlWord.Ctrl_BRDRTHTNSG:
case RtfControlWord.Ctrl_BRDRTNTHLG:
case RtfControlWord.Ctrl_BRDRTNTHMG:
case RtfControlWord.Ctrl_BRDRTNTHSG:
case RtfControlWord.Ctrl_BRDRTNTHTNLG:
case RtfControlWord.Ctrl_BRDRTNTHTNMG:
case RtfControlWord.Ctrl_BRDRTNTHTNSG:
case RtfControlWord.Ctrl_BRDRTRIPLE:
case RtfControlWord.Ctrl_BRDRW:
case RtfControlWord.Ctrl_BRDRWAVY:
case RtfControlWord.Ctrl_BRDRWAVYDB:
case RtfControlWord.Ctrl_BRSP:
case RtfControlWord.Ctrl_BOX:
case RtfControlWord.Ctrl_RTLROW:
case RtfControlWord.Ctrl_LTRROW:
case RtfControlWord.Ctrl_TRSPDB:
case RtfControlWord.Ctrl_TRSPDFB:
case RtfControlWord.Ctrl_TRSPDFL:
case RtfControlWord.Ctrl_TRSPDFR:
case RtfControlWord.Ctrl_TRSPDFT:
case RtfControlWord.Ctrl_TRSPDL:
case RtfControlWord.Ctrl_TRSPDR:
case RtfControlWord.Ctrl_TRSPDT:
case RtfControlWord.Ctrl_CLMGF:
case RtfControlWord.Ctrl_CLMRG:
case RtfControlWord.Ctrl_CLVMGF:
case RtfControlWord.Ctrl_CLVMRG:
HandleTableProperties(token, formatState);
break;
// Symbols
case RtfControlWord.Ctrl_TAB:
ProcessText("\t");
break;
case RtfControlWord.Ctrl_EMDASH:
ProcessText("\x2014");
break;
case RtfControlWord.Ctrl_ENDASH:
ProcessText("\x2013");
break;
case RtfControlWord.Ctrl_EMSPACE:
ProcessText("\x2003");
break;
case RtfControlWord.Ctrl_ENSPACE:
ProcessText("\x2002");
break;
case RtfControlWord.Ctrl_QMSPACE:
ProcessText("\x2005");
break;
case RtfControlWord.Ctrl_BULLET:
ProcessText("\x2022"); // Unicode bullet
break;
case RtfControlWord.Ctrl_LQUOTE:
ProcessText("\x2018");
break;
case RtfControlWord.Ctrl_RQUOTE:
ProcessText("\x2019");
break;
case RtfControlWord.Ctrl_LDBLQUOTE:
ProcessText("\x201c");
break;
case RtfControlWord.Ctrl_RDBLQUOTE:
ProcessText("\x201d");
break;
case RtfControlWord.Ctrl_ZWJ: // zero-width-joiner
ProcessText("\x200d");
break;
case RtfControlWord.Ctrl_ZWNJ: // zero-width-non-joiner
ProcessText("\x200c");
break;
case RtfControlWord.Ctrl_ANSICPG:
// This is just a hint for RTF output roundtripping - ignore.
break;
// CodePage Tokens
case RtfControlWord.Ctrl_ANSI:
case RtfControlWord.Ctrl_MAC:
case RtfControlWord.Ctrl_PC:
case RtfControlWord.Ctrl_PCA:
case RtfControlWord.Ctrl_UPR:
case RtfControlWord.Ctrl_UD:
case RtfControlWord.Ctrl_UC:
HandleCodePageTokens(token, formatState);
break;
case RtfControlWord.Ctrl_U:
HandleCodePageTokens(token, formatState);
_lexer.AdvanceForUnicode(formatState.UnicodeSkip);
break;
// Field (hyperlink) commands
case RtfControlWord.Ctrl_FIELD:
case RtfControlWord.Ctrl_FLDRSLT:
case RtfControlWord.Ctrl_FLDINST:
case RtfControlWord.Ctrl_FLDPRIV:
HandleFieldTokens(token, formatState);
break;
// Structure commands
case RtfControlWord.Ctrl_ILVL:
formatState.ILVL = token.Parameter;
break;
case RtfControlWord.Ctrl_INTBL:
formatState.IsInTable = token.FlagValue;
break;
case RtfControlWord.Ctrl_LS:
if (formatState.RtfDestination == RtfDestination.DestListOverride)
{
HandleListTokens(token, formatState);
}
else
{
formatState.ILS = token.Parameter;
}
break;
case RtfControlWord.Ctrl_ITAP:
formatState.ITAP = token.Parameter;
break;
// Other special parsing commands
case RtfControlWord.Ctrl_BIN:
HandleBinControl(token, formatState);
break;
// List Table commands
case RtfControlWord.Ctrl_LIST:
case RtfControlWord.Ctrl_LISTTEMPLATEID:
case RtfControlWord.Ctrl_LISTHYBRID:
case RtfControlWord.Ctrl_LISTSIMPLE:
case RtfControlWord.Ctrl_LISTLEVEL:
case RtfControlWord.Ctrl_LISTTEXT:
case RtfControlWord.Ctrl_LEVELNFC:
case RtfControlWord.Ctrl_LEVELNFCN:
case RtfControlWord.Ctrl_LEVELJC:
case RtfControlWord.Ctrl_LEVELJCN:
case RtfControlWord.Ctrl_LEVELFOLLOW:
case RtfControlWord.Ctrl_LEVELSTARTAT:
case RtfControlWord.Ctrl_LEVELSPACE:
case RtfControlWord.Ctrl_LEVELINDENT:
case RtfControlWord.Ctrl_LEVELTEXT:
case RtfControlWord.Ctrl_LEVELTEMPLATEID:
case RtfControlWord.Ctrl_LISTID:
case RtfControlWord.Ctrl_LEVELNUMBERS:
case RtfControlWord.Ctrl_LISTOVERRIDE:
HandleListTokens(token, formatState);
break;
case RtfControlWord.Ctrl_LISTPICTURE:
formatState.RtfDestination = RtfDestination.DestListPicture;
break;
// Old-style list info
case RtfControlWord.Ctrl_PNLVL:
case RtfControlWord.Ctrl_PNLVLBLT:
case RtfControlWord.Ctrl_PNLVLBODY:
case RtfControlWord.Ctrl_PNLVLCONT:
case RtfControlWord.Ctrl_PNCARD:
case RtfControlWord.Ctrl_PNDEC:
case RtfControlWord.Ctrl_PNUCLTR:
case RtfControlWord.Ctrl_PNUCRM:
case RtfControlWord.Ctrl_PNLCLTR:
case RtfControlWord.Ctrl_PNLCRM:
case RtfControlWord.Ctrl_PNORD:
case RtfControlWord.Ctrl_PNORDT:
case RtfControlWord.Ctrl_PNBIDIA:
case RtfControlWord.Ctrl_PNBIDIB:
case RtfControlWord.Ctrl_PN:
case RtfControlWord.Ctrl_PNTXTA:
case RtfControlWord.Ctrl_PNTXTB:
case RtfControlWord.Ctrl_PNTEXT:
case RtfControlWord.Ctrl_PNSTART:
HandleOldListTokens(token, formatState);
break;
// Shapes (we only read down-level info)
case RtfControlWord.Ctrl_DO:
case RtfControlWord.Ctrl_SHPRSLT:
case RtfControlWord.Ctrl_DPTXBXTEXT:
case RtfControlWord.Ctrl_SHPPICT:
case RtfControlWord.Ctrl_NONSHPPICT:
HandleShapeTokens(token, formatState);
break;
// Document tables
case RtfControlWord.Ctrl_FONTTBL:
formatState.RtfDestination = RtfDestination.DestFontTable;
break;
case RtfControlWord.Ctrl_COLORTBL:
formatState.RtfDestination = RtfDestination.DestColorTable;
break;
case RtfControlWord.Ctrl_LISTTABLE:
formatState.RtfDestination = RtfDestination.DestListTable;
break;
case RtfControlWord.Ctrl_LISTOVERRIDETABLE:
formatState.RtfDestination = RtfDestination.DestListOverrideTable;
break;
case RtfControlWord.Ctrl_RED:
if (formatState.RtfDestination == RtfDestination.DestColorTable)
{
_converterState.ColorTable.NewRed = (byte)token.Parameter;
}
break;
case RtfControlWord.Ctrl_GREEN:
if (formatState.RtfDestination == RtfDestination.DestColorTable)
_converterState.ColorTable.NewGreen = (byte)token.Parameter;
break;
case RtfControlWord.Ctrl_BLUE:
if (formatState.RtfDestination == RtfDestination.DestColorTable)
{
_converterState.ColorTable.NewBlue = (byte)token.Parameter;
}
break;
case RtfControlWord.Ctrl_SHPINST:
formatState.RtfDestination = RtfDestination.DestShapeInstruction;
break;
case RtfControlWord.Ctrl_PICT:
{
FormatState previousFormatState = _converterState.PreviousTopFormatState(1);
// Filter the redundant picture or list picture
if ((previousFormatState.RtfDestination == RtfDestination.DestShapePicture ||
previousFormatState.RtfDestination == RtfDestination.DestShapeInstruction) ||
(previousFormatState.RtfDestination != RtfDestination.DestNoneShapePicture &&
previousFormatState.RtfDestination != RtfDestination.DestShape &&
previousFormatState.RtfDestination != RtfDestination.DestListPicture))
{
formatState.RtfDestination = RtfDestination.DestPicture;
}
}
break;
case RtfControlWord.Ctrl_PNGBLIP:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageFormat = RtfImageFormat.Png;
}
break;
case RtfControlWord.Ctrl_JPEGBLIP:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageFormat = RtfImageFormat.Jpeg;
}
break;
case RtfControlWord.Ctrl_WMETAFILE:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageFormat = RtfImageFormat.Wmf;
}
break;
case RtfControlWord.Ctrl_DN:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
// DN comes in half points
formatState.ImageBaselineOffset = Converters.HalfPointToPositivePx(token.Parameter);
formatState.IncludeImageBaselineOffset = true;
}
break;
case RtfControlWord.Ctrl_PICHGOAL:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageHeight = Converters.TwipToPositivePx(token.Parameter);
}
break;
case RtfControlWord.Ctrl_PICWGOAL:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageWidth = Converters.TwipToPositivePx(token.Parameter);
}
break;
case RtfControlWord.Ctrl_PICSCALEX:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageScaleWidth = token.Parameter;
}
break;
case RtfControlWord.Ctrl_PICSCALEY:
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.ImageScaleHeight = token.Parameter;
}
break;
}
}
internal void ProcessText(RtfToken token)
{
FormatState fs = _converterState.TopFormatState;
if (fs.IsHidden)
{
return;
}
switch (fs.RtfDestination)
{
case RtfDestination.DestFieldResult:
case RtfDestination.DestShapeResult:
case RtfDestination.DestNormal:
case RtfDestination.DestListText:
HandleNormalText(token.Text, fs);
break;
case RtfDestination.DestFontTable:
case RtfDestination.DestFontName:
ProcessFontTableText(token);
break;
case RtfDestination.DestColorTable:
ProcessColorTableText(token);
break;
case RtfDestination.DestField:
case RtfDestination.DestFieldInstruction:
case RtfDestination.DestFieldPrivate:
ProcessFieldText(token);
break;
}
}
internal void ProcessTextSymbol(RtfToken token)
{
// Quoted control character (be generous) - should be limited to "'-*;\_{|}~"
if (token.Text.Length == 0)
{
return;
}
// Set token text with the quoted control character
SetTokenTextWithControlCharacter(token);
switch (_converterState.TopFormatState.RtfDestination)
{
case RtfDestination.DestNormal:
case RtfDestination.DestFieldResult:
case RtfDestination.DestShapeResult:
case RtfDestination.DestListText:
HandleNormalText(token.Text, _converterState.TopFormatState);
break;
case RtfDestination.DestFontTable:
ProcessFontTableText(token);
break;
case RtfDestination.DestColorTable:
ProcessColorTableText(token);
break;
case RtfDestination.DestField:
case RtfDestination.DestFieldInstruction:
case RtfDestination.DestFieldPrivate:
ProcessFieldText(token);
break;
}
}
internal void HandleBinControl(RtfToken token, FormatState formatState)
{
if (token.Parameter > 0)
{
if (formatState.RtfDestination == RtfDestination.DestPicture)
{
formatState.IsImageDataBinary = true;
}
else
{
_lexer.AdvanceForBinary((int)token.Parameter);
}
}
}
internal void HandlePara(RtfToken token, FormatState formatState)
{
// Ignore \par in other destinations
if (!formatState.IsContentDestination || formatState.IsHidden)
{
return;
}
// Arrival of paragraph tag allows us to rationalize structure information.
// Various tags have told us things about this paragraph:
// \INTBL: Paragraph is inside a table cell.
// \ITAP{N}: Nesting level for table. Note that I may not have yet
// seen any tags that start the nested table.
// \LS{N}: Listtable override index. Any value indicates this paragraph
// is in a list.
// \ILVL: Nesting level for list.
// If we're in a throw-away destination, just return.
HandleParagraphFromText(formatState);
// Make sure proper number of tables are open to reflect this paragraphs nest level
HandleTableNesting(formatState);
// Now handle lists, which always behave as if they are inside tables.
HandleListNesting(formatState);
}
internal void WrapPendingInlineInParagraph(RtfToken token, FormatState formatState)
{
// Ignore \par in other destinations
if (!formatState.IsContentDestination || formatState.IsHidden)
{
return;
}
// Only treat \page as \par if there is already some inline content. In normal cases,
// (e.g. Word output) a page break comes between paragraphs and we don't want to emit
// anything extra in that case.
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dn;
// Insert the paragraph before any text or inline nodes at the top of the stack.
int nNoOpValue = dna.Count;
int nInsertAt = dna.Count; // Default insertion location
for (; nInsertAt > 0; nInsertAt--)
{
dn = dna.EntryAt(nInsertAt - 1);
if (!dn.IsInline || dn.ClosedParent != null || !dn.IsMatched)
{
break;
}
// If we only have listtext on the stack, don't force a para.
else if (dn.Type == DocumentNodeType.dnListText && !dn.IsPending
&& nInsertAt + dn.ChildCount == dna.Count)
{
nNoOpValue = nInsertAt - 1;
}
}
// If there are no inline nodes, don't generate extra content.
if (nInsertAt == nNoOpValue)
{
return;
}
// Otherwise, just treat as paragraph mark
HandlePara(token, formatState);
}
internal void HandlePage(RtfToken token, FormatState formatState)
{
WrapPendingInlineInParagraph(token, formatState);
}
internal void HandleParagraphFromText(FormatState formatState)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dn;
// Insert the paragraph before any text or inline nodes at the top of the stack.
int nInsertAt = dna.Count; // Default insertion location
for (; nInsertAt > 0; nInsertAt--)
{
dn = dna.EntryAt(nInsertAt - 1);
if (!dn.IsInline
|| (dn.ClosedParent != null && !dn.ClosedParent.IsInline)
|| !dn.IsMatched)
{
break;
}
}
dn = new DocumentNode(DocumentNodeType.dnParagraph);
dn.FormatState = new FormatState(formatState);
dn.ConstrainFontPropagation(formatState);
dna.InsertNode(nInsertAt, dn);
// Now close immediately.
dna.CloseAt(nInsertAt);
dna.CoalesceOnlyChildren(_converterState, nInsertAt);
}
internal void WrapInlineInParagraph(int nInsertAt, int nChildren)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
Debug.Assert(nInsertAt >= 0 && nChildren > 0 && nInsertAt + nChildren - 1 < dna.Count);
DocumentNode dnChild = dna.EntryAt(nInsertAt + nChildren - 1);
DocumentNode dn = new DocumentNode(DocumentNodeType.dnParagraph);
dn.FormatState = new FormatState(dnChild.FormatState);
dn.ConstrainFontPropagation(dn.FormatState);
DocumentNode dnParent = null;
// Parent shouldn't be the child of new inserted document node
// to avoid the infinite loop on InsertChildAt()
if (dnChild.ClosedParent != null
&& dnChild.ClosedParent.Index < nInsertAt
&& dnChild.ClosedParent.Index > (nInsertAt + nChildren - 1))
{
dnParent = dnChild.ClosedParent;
}
dna.InsertChildAt(dnParent, dn, nInsertAt, nChildren);
dna.CoalesceOnlyChildren(_converterState, nInsertAt);
}
internal void ProcessPendingTextAtRowEnd()
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
// Remove any pending inline content before closing the row.
int nExcise = 0;
for (int i = dna.Count - 1; i >= 0; i--)
{
DocumentNode dn = dna.EntryAt(i);
if (dn.IsInline && dn.ClosedParent == null)
{
nExcise++;
}
else
{
break;
}
}
if (nExcise > 0)
{
dna.Excise(dna.Count - nExcise, nExcise);
}
}
internal void HandleTableTokens(RtfToken token, FormatState formatState)
{
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsOld = _converterState.PreviousTopFormatState(1);
if (fsCur == null || fsOld == null)
{
return;
}
// Propagate current destination into nested table props destination keyword
if (token.RtfControlWordInfo.Control == RtfControlWord.Ctrl_NESTTABLEPROPS)
{
fsCur.RtfDestination = fsOld.RtfDestination;
}
if (!formatState.IsContentDestination)
{
return;
}
DocumentNodeArray dna = _converterState.DocumentNodeArray;
int nCellAt;
bool bOldHide = formatState.IsHidden;
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_CELL:
// Force a paragraph
// Set intbl and itap values, then use paragraph code.
formatState.IsInTable = true;
formatState.ITAP = 1;
formatState.IsHidden = false;
HandlePara(token, formatState);
formatState.IsHidden = bOldHide;
nCellAt = dna.FindPending(DocumentNodeType.dnCell);
if (nCellAt >= 0)
{
dna.CloseAt(nCellAt);
// Don't coalesce cell tag itself, since I might not have yet read the properties
// I need for writing out the cell attributes.
// Actually, even paragraph tags inside cell needs to have table info.
// dna.CoalesceOnlyChildren(_converterState, nCellAt);
}
break;
case RtfControlWord.Ctrl_NESTCELL:
// Force a paragraph out.
formatState.IsHidden = false;
HandlePara(token, formatState);
formatState.IsHidden = bOldHide;
// If we encounter an open row before an open cell, we need to open a new cell.
// Or if we only have one level of table currently open.
int nOpenCells = dna.CountOpenCells();
DocumentNodeType scope = dna.GetTableScope();
if (scope != DocumentNodeType.dnCell || nOpenCells < 2)
{
HandlePara(token, formatState);
}
// Now close the currently open cell
nCellAt = dna.FindPending(DocumentNodeType.dnCell);
if (nCellAt >= 0)
{
dna.CloseAt(nCellAt);
// Don't coalesce cell tag itself, since I might not have yet read the properties
// I need for writing out the cell attributes.
// Actually, even paragraph tags inside cell needs to have table info.
// dna.CoalesceOnlyChildren(_converterState, nCellAt);
}
break;
case RtfControlWord.Ctrl_TROWD:
formatState.IsHidden = false;
formatState.SetRowDefaults();
formatState.IsHidden = bOldHide;
break;
case RtfControlWord.Ctrl_ROW:
case RtfControlWord.Ctrl_NESTROW:
// Word puts out \row properties both before and after the cell contents.
// \nestrow properties are only put out *after* the cell contents.
formatState.IsHidden = false;
int nRowAt = dna.FindPending(DocumentNodeType.dnRow);
if (nRowAt >= 0)
{
DocumentNode dnRow = dna.EntryAt(nRowAt);
if (formatState.RowFormat != null)
{
dnRow.FormatState.RowFormat = new RowFormat(formatState.RowFormat);
dnRow.FormatState.RowFormat.CanonicalizeWidthsFromRTF();
// Also cache the row information in the table node
int nTable = dna.FindPendingFrom(DocumentNodeType.dnTable, nRowAt - 1, -1);
if (nTable >= 0)
{
DocumentNode dnTable = dna.EntryAt(nTable);
if (!dnTable.FormatState.HasRowFormat)
{
dnTable.FormatState.RowFormat = dnRow.FormatState.RowFormat;
}
}
}
// Anomalous, but possible for illegal content.
ProcessPendingTextAtRowEnd();
dna.CloseAt(nRowAt);
// Don't coalesce - I need to examine all cells before writing things out
// dna.CoalesceChildren(_converterState, nRowAt);
}
formatState.IsHidden = bOldHide;
break;
case RtfControlWord.Ctrl_NESTTABLEPROPS:
// Handled above.
break;
}
}
internal ListOverride GetControllingListOverride()
{
ListTable lt = _converterState.ListTable;
ListOverrideTable lot = _converterState.ListOverrideTable;
RtfFormatStack formats = _converterState.RtfFormatStack;
for (int i = formats.Count - 1; i >= 0; i--)
{
FormatState fs = formats.EntryAt(i);
if (fs.RtfDestination == RtfDestination.DestListOverride)
{
return lot.CurrentEntry;
}
else if (fs.RtfDestination == RtfDestination.DestListTable)
{
return null;
}
}
return null;
}
internal ListLevelTable GetControllingLevelTable()
{
ListTable lt = _converterState.ListTable;
ListOverrideTable lot = _converterState.ListOverrideTable;
RtfFormatStack formats = _converterState.RtfFormatStack;
for (int i = formats.Count - 1; i >= 0; i--)
{
FormatState fs = formats.EntryAt(i);
if (fs.RtfDestination == RtfDestination.DestListOverride)
{
ListOverride lo = lot.CurrentEntry;
if (lo.Levels == null)
{
lo.Levels = new ListLevelTable();
}
return lo.Levels;
}
else if (fs.RtfDestination == RtfDestination.DestListTable)
{
ListTableEntry lte = lt.CurrentEntry;
if (lte != null)
{
return lte.Levels;
}
}
}
return null;
}
internal void HandleListTokens(RtfToken token, FormatState formatState)
{
ListTable listTable = _converterState.ListTable;
ListOverrideTable listOverrideTable = _converterState.ListOverrideTable;
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsOld = _converterState.PreviousTopFormatState(1);
if (fsCur == null || fsOld == null)
{
return;
}
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_LIST:
if (formatState.RtfDestination == RtfDestination.DestListTable)
{
ListTableEntry listTableEntry = listTable.AddEntry();
}
break;
case RtfControlWord.Ctrl_LISTTEMPLATEID:
{
ListTableEntry listTableEntry = listTable.CurrentEntry;
if (listTableEntry != null)
{
listTableEntry.TemplateID = token.Parameter;
}
}
break;
case RtfControlWord.Ctrl_LISTHYBRID:
case RtfControlWord.Ctrl_LISTSIMPLE:
{
ListTableEntry listTableEntry = listTable.CurrentEntry;
if (listTableEntry != null)
{
listTableEntry.Simple = token.RtfControlWordInfo.Control == RtfControlWord.Ctrl_LISTSIMPLE;
}
}
break;
case RtfControlWord.Ctrl_LISTLEVEL:
{
formatState.RtfDestination = RtfDestination.DestListLevel;
ListLevelTable levels = GetControllingLevelTable();
if (levels != null)
{
ListLevel listLevel = levels.AddEntry();
}
}
break;
case RtfControlWord.Ctrl_LISTTEXT:
if (fsOld.IsContentDestination || formatState.IsHidden)
{
formatState.RtfDestination = RtfDestination.DestListText;
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnl = new DocumentNode(DocumentNodeType.dnListText);
dnl.FormatState = new FormatState(formatState);
dna.Push(dnl);
}
break;
case RtfControlWord.Ctrl_LEVELNFC:
case RtfControlWord.Ctrl_LEVELNFCN:
{
ListLevelTable levels = GetControllingLevelTable();
if (levels != null)
{
ListLevel listLevel = levels.CurrentEntry;
if (listLevel != null)
{
listLevel.Marker = (MarkerStyle)token.Parameter;
}
}
}
break;
case RtfControlWord.Ctrl_LEVELJC:
case RtfControlWord.Ctrl_LEVELJCN:
// NB: Marker alignment not supported in XAML.
break;
case RtfControlWord.Ctrl_LEVELFOLLOW:
break;
case RtfControlWord.Ctrl_LEVELSTARTAT:
{
ListLevelTable levels = GetControllingLevelTable();
if (levels != null)
{
ListLevel listLevel = levels.CurrentEntry;
if (listLevel != null)
{
listLevel.StartIndex = token.Parameter;
}
else
{
// This is the case where the list override *only* specifies startat override.
ListOverride lo = GetControllingListOverride();
if (lo != null)
{
lo.StartIndex = token.Parameter;
}
}
}
}
break;
case RtfControlWord.Ctrl_LEVELSPACE:
break;
case RtfControlWord.Ctrl_LEVELINDENT:
break;
case RtfControlWord.Ctrl_LEVELTEXT:
break;
case RtfControlWord.Ctrl_LEVELTEMPLATEID:
break;
case RtfControlWord.Ctrl_LISTID:
{
if (formatState.RtfDestination == RtfDestination.DestListOverride)
{
ListOverride listOverride = listOverrideTable.CurrentEntry;
if (listOverride != null)
{
listOverride.ID = token.Parameter;
}
}
else
{
ListTableEntry listTableEntry = listTable.CurrentEntry;
if (listTableEntry != null)
{
listTableEntry.ID = token.Parameter;
}
}
}
break;
case RtfControlWord.Ctrl_LEVELNUMBERS:
break;
case RtfControlWord.Ctrl_LISTOVERRIDE:
{
FormatState previousFormatState = _converterState.PreviousTopFormatState(1);
if (previousFormatState.RtfDestination == RtfDestination.DestListOverrideTable)
{
formatState.RtfDestination = RtfDestination.DestListOverride;
ListOverride listOverride = listOverrideTable.AddEntry();
}
}
break;
case RtfControlWord.Ctrl_LS:
if (formatState.RtfDestination == RtfDestination.DestListOverride)
{
ListOverride listOverride = listOverrideTable.CurrentEntry;
if (listOverride != null)
{
listOverride.Index = token.Parameter;
}
}
break;
}
}
internal void HandleShapeTokens(RtfToken token, FormatState formatState)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsOld = _converterState.PreviousTopFormatState(1);
if (fsCur == null || fsOld == null)
{
return;
}
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_DO:
// Just propagate destination through this keyword.
fsCur.RtfDestination = fsOld.RtfDestination;
break;
case RtfControlWord.Ctrl_SHPRSLT:
if (fsOld.IsContentDestination)
{
fsCur.RtfDestination = RtfDestination.DestShape;
}
break;
case RtfControlWord.Ctrl_DPTXBXTEXT:
if (fsOld.IsContentDestination)
{
// Track the destination so I can recognize when we leave this scope.
fsCur.RtfDestination = RtfDestination.DestShapeResult;
// Wrap any inline content that occurs before this shape anchor in a paragraph,
// since the shape content itself will be block level.
WrapPendingInlineInParagraph(token, formatState);
DocumentNodeType t = dna.GetTableScope();
if (t != DocumentNodeType.dnParagraph)
{
if (t == DocumentNodeType.dnTableBody)
{
// If row has been closed, close overall table as well.
int nAt = dna.FindPending(DocumentNodeType.dnTable);
if (nAt >= 0)
{
dna.CloseAt(nAt);
dna.CoalesceChildren(_converterState, nAt);
}
}
else
{
// If I'm inside a table, reopen last cell to insert shape contents. Otherwise
// table gets torqued.
dna.OpenLastCell();
}
}
// The shape node generates no output but plays a large role in changing the
// behavior of the "FindPending" routines to keep from looking outside this scope.
DocumentNode dn = new DocumentNode(DocumentNodeType.dnShape);
formatState.SetParaDefaults();
formatState.SetCharDefaults();
dn.FormatState = new FormatState(formatState);
dna.Push(dn);
}
break;
case RtfControlWord.Ctrl_SHPPICT:
// If this occurs in listtext context, mark listtext as non-empty.
int ndnListText = dna.FindPending(DocumentNodeType.dnListText);
if (ndnListText >= 0)
{
DocumentNode dnListText = dna.EntryAt(ndnListText);
dnListText.HasMarkerContent = true;
}
// Keep the rtf destination as the list picture to skip the list picture
if (fsOld.RtfDestination == RtfDestination.DestListPicture)
{
formatState.RtfDestination = RtfDestination.DestListPicture;
}
else
{
formatState.RtfDestination = RtfDestination.DestShapePicture;
}
break;
case RtfControlWord.Ctrl_NONSHPPICT:
formatState.RtfDestination = RtfDestination.DestNoneShapePicture;
break;
}
}
internal void HandleOldListTokens(RtfToken token, FormatState formatState)
{
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsOld = _converterState.PreviousTopFormatState(1);
if (fsCur == null || fsOld == null)
{
return;
}
// If we're in the PN destination, we push marker setting into the previous format state.
if (formatState.RtfDestination == RtfDestination.DestPN)
{
formatState = fsOld;
}
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_PNLVL:
formatState.PNLVL = token.Parameter;
break;
case RtfControlWord.Ctrl_PNLVLBLT:
formatState.Marker = MarkerStyle.MarkerBullet;
formatState.IsContinue = false;
break;
case RtfControlWord.Ctrl_PNLVLBODY:
formatState.Marker = MarkerStyle.MarkerArabic;
formatState.IsContinue = false;
break;
case RtfControlWord.Ctrl_PNLVLCONT:
formatState.IsContinue = true;
break;
case RtfControlWord.Ctrl_PNCARD:
formatState.Marker = MarkerStyle.MarkerCardinal;
break;
case RtfControlWord.Ctrl_PNDEC:
formatState.Marker = MarkerStyle.MarkerArabic;
break;
case RtfControlWord.Ctrl_PNUCLTR:
formatState.Marker = MarkerStyle.MarkerUpperAlpha;
break;
case RtfControlWord.Ctrl_PNUCRM:
formatState.Marker = MarkerStyle.MarkerUpperRoman;
break;
case RtfControlWord.Ctrl_PNLCLTR:
formatState.Marker = MarkerStyle.MarkerLowerAlpha;
break;
case RtfControlWord.Ctrl_PNLCRM:
formatState.Marker = MarkerStyle.MarkerLowerRoman;
break;
case RtfControlWord.Ctrl_PNORD:
formatState.Marker = MarkerStyle.MarkerOrdinal;
break;
case RtfControlWord.Ctrl_PNORDT:
formatState.Marker = MarkerStyle.MarkerOrdinal;
break;
case RtfControlWord.Ctrl_PNBIDIA:
formatState.Marker = MarkerStyle.MarkerArabic;
break;
case RtfControlWord.Ctrl_PNBIDIB:
formatState.Marker = MarkerStyle.MarkerArabic;
break;
case RtfControlWord.Ctrl_PN:
formatState.RtfDestination = RtfDestination.DestPN;
fsOld.Marker = MarkerStyle.MarkerBullet;
break;
case RtfControlWord.Ctrl_PNTXTA:
// Leave with unknown destination so text is tossed.
break;
case RtfControlWord.Ctrl_PNTXTB:
// Leave with unknown destination so text is tossed.
break;
case RtfControlWord.Ctrl_PNTEXT:
if (fsOld.IsContentDestination || formatState.IsHidden)
{
fsCur.RtfDestination = RtfDestination.DestListText;
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnl = new DocumentNode(DocumentNodeType.dnListText);
dnl.FormatState = new FormatState(formatState);
dna.Push(dnl);
}
break;
case RtfControlWord.Ctrl_PNSTART:
formatState.StartIndex = token.Parameter;
break;
default:
formatState.Marker = MarkerStyle.MarkerBullet;
break;
}
}
internal void HandleTableProperties(RtfToken token, FormatState formatState)
{
if (!formatState.IsContentDestination)
{
return;
}
CellFormat cf = null;
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_TRGAPH:
break;
case RtfControlWord.Ctrl_TRLEFT:
formatState.RowFormat.Trleft = token.Parameter;
break;
case RtfControlWord.Ctrl_TRQC:
// Specifies overall alignment of the table row - ignore for now.
break;
case RtfControlWord.Ctrl_TRQL:
// Specifies overall alignment of the table row - ignore for now.
break;
case RtfControlWord.Ctrl_TRQR:
// Specifies overall alignment of the table row - ignore for now.
break;
case RtfControlWord.Ctrl_TRPADDL:
formatState.RowFormat.RowCellFormat.PaddingLeft = token.Parameter;
break;
case RtfControlWord.Ctrl_TRPADDR:
formatState.RowFormat.RowCellFormat.PaddingRight = token.Parameter;
break;
case RtfControlWord.Ctrl_TRPADDB:
formatState.RowFormat.RowCellFormat.PaddingBottom = token.Parameter;
break;
case RtfControlWord.Ctrl_TRPADDT:
formatState.RowFormat.RowCellFormat.PaddingTop = token.Parameter;
break;
case RtfControlWord.Ctrl_TRPADDFL:
// zero value indicates ignore trpaddl, three means treat it as twips
break;
case RtfControlWord.Ctrl_TRPADDFT:
// zero value indicates ignore trpaddt, three means treat it as twips
break;
case RtfControlWord.Ctrl_TRPADDFB:
// zero value indicates ignore trpaddb, three means treat it as twips
break;
case RtfControlWord.Ctrl_TRPADDFR:
// zero value indicates ignore trpaddr, three means treat it as twips
break;
case RtfControlWord.Ctrl_TRSPDFB:
if (token.Parameter == 0)
formatState.RowFormat.RowCellFormat.SpacingBottom = 0;
break;
case RtfControlWord.Ctrl_TRSPDFL:
if (token.Parameter == 0)
formatState.RowFormat.RowCellFormat.SpacingLeft = 0;
break;
case RtfControlWord.Ctrl_TRSPDFR:
if (token.Parameter == 0)
formatState.RowFormat.RowCellFormat.SpacingRight = 0;
break;
case RtfControlWord.Ctrl_TRSPDFT:
if (token.Parameter == 0)
formatState.RowFormat.RowCellFormat.SpacingTop = 0;
break;
case RtfControlWord.Ctrl_TRSPDB:
formatState.RowFormat.RowCellFormat.SpacingBottom = token.Parameter;
break;
case RtfControlWord.Ctrl_TRSPDL:
formatState.RowFormat.RowCellFormat.SpacingLeft = token.Parameter;
break;
case RtfControlWord.Ctrl_TRSPDR:
formatState.RowFormat.RowCellFormat.SpacingRight = token.Parameter;
break;
case RtfControlWord.Ctrl_TRSPDT:
formatState.RowFormat.RowCellFormat.SpacingTop = token.Parameter;
break;
case RtfControlWord.Ctrl_TRWWIDTH:
// Row (table) width
formatState.RowFormat.WidthRow.Value = token.Parameter;
break;
case RtfControlWord.Ctrl_TRFTSWIDTH:
// Units for WWIDTH (0 - ignore, 1 - auto, 2 - 50ths of a percent, 3 - twips)
if (Validators.IsValidWidthType(token.Parameter))
formatState.RowFormat.WidthRow.Type = (WidthType)token.Parameter;
break;
case RtfControlWord.Ctrl_TRWWIDTHB:
// Space before row width
break;
case RtfControlWord.Ctrl_TRFTSWIDTHB:
break;
case RtfControlWord.Ctrl_TRWWIDTHA:
// Space after row width
formatState.RowFormat.WidthA.Value = token.Parameter;
break;
case RtfControlWord.Ctrl_TRFTSWIDTHA:
if (Validators.IsValidWidthType(token.Parameter))
formatState.RowFormat.WidthA.Type = (WidthType)token.Parameter;
break;
case RtfControlWord.Ctrl_TRAUTOFIT:
if (token.ToggleValue > 0)
formatState.RowFormat.WidthRow.SetDefaults();
break;
case RtfControlWord.Ctrl_CLWWIDTH:
cf = formatState.RowFormat.CurrentCellFormat();
cf.Width.Value = token.Parameter;
break;
case RtfControlWord.Ctrl_CLFTSWIDTH:
cf = formatState.RowFormat.CurrentCellFormat();
if (Validators.IsValidWidthType(token.Parameter))
cf.Width.Type = (WidthType)token.Parameter;
break;
case RtfControlWord.Ctrl_TRBRDRT:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderTop;
break;
case RtfControlWord.Ctrl_TRBRDRB:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderBottom;
break;
case RtfControlWord.Ctrl_TRBRDRR:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderRight;
break;
case RtfControlWord.Ctrl_TRBRDRL:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderLeft;
break;
case RtfControlWord.Ctrl_TRBRDRV:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderLeft;
break;
case RtfControlWord.Ctrl_TRBRDRH:
ConverterState.CurrentBorder = formatState.RowFormat.RowCellFormat.BorderTop;
break;
case RtfControlWord.Ctrl_CLVERTALT:
cf = formatState.RowFormat.CurrentCellFormat();
cf.VAlign = VAlign.AlignTop;
break;
case RtfControlWord.Ctrl_CLVERTALB:
cf = formatState.RowFormat.CurrentCellFormat();
cf.VAlign = VAlign.AlignBottom;
break;
case RtfControlWord.Ctrl_CLVERTALC:
cf = formatState.RowFormat.CurrentCellFormat();
cf.VAlign = VAlign.AlignCenter;
break;
case RtfControlWord.Ctrl_CLSHDNG:
// Cell shading in 100's of a percent
cf = formatState.RowFormat.CurrentCellFormat();
cf.Shading = token.Parameter;
break;
case RtfControlWord.Ctrl_CLSHDRAWNIL:
// No cell shading - clear color for now
cf = formatState.RowFormat.CurrentCellFormat();
cf.Shading = -1;
cf.CB = -1;
cf.CF = -1;
break;
case RtfControlWord.Ctrl_CLBRDRB:
cf = formatState.RowFormat.CurrentCellFormat();
ConverterState.CurrentBorder = cf.BorderBottom;
break;
case RtfControlWord.Ctrl_CLBRDRR:
cf = formatState.RowFormat.CurrentCellFormat();
ConverterState.CurrentBorder = cf.BorderRight;
break;
case RtfControlWord.Ctrl_CLBRDRT:
cf = formatState.RowFormat.CurrentCellFormat();
ConverterState.CurrentBorder = cf.BorderTop;
break;
case RtfControlWord.Ctrl_CLBRDRL:
cf = formatState.RowFormat.CurrentCellFormat();
ConverterState.CurrentBorder = cf.BorderLeft;
break;
case RtfControlWord.Ctrl_CLCBPAT:
cf = formatState.RowFormat.CurrentCellFormat();
cf.CB = token.Parameter;
break;
case RtfControlWord.Ctrl_CLCFPAT:
cf = formatState.RowFormat.CurrentCellFormat();
cf.CF = token.Parameter;
break;
case RtfControlWord.Ctrl_CLPADL:
cf = formatState.RowFormat.CurrentCellFormat();
cf.PaddingLeft = token.Parameter;
break;
case RtfControlWord.Ctrl_CLPADR:
cf = formatState.RowFormat.CurrentCellFormat();
cf.PaddingRight = token.Parameter;
break;
case RtfControlWord.Ctrl_CLPADB:
cf = formatState.RowFormat.CurrentCellFormat();
cf.PaddingBottom = token.Parameter;
break;
case RtfControlWord.Ctrl_CLPADT:
cf = formatState.RowFormat.CurrentCellFormat();
cf.PaddingTop = token.Parameter;
break;
case RtfControlWord.Ctrl_CLPADFL:
// zero value indicates ignore clpadl, three means treat it as twips
break;
case RtfControlWord.Ctrl_CLPADFT:
// zero value indicates ignore clpadt, three means treat it as twips
break;
case RtfControlWord.Ctrl_CLPADFB:
// zero value indicates ignore clpadb, three means treat it as twips
break;
case RtfControlWord.Ctrl_CLPADFR:
// zero value indicates ignore clpadr, three means treat it as twips
break;
case RtfControlWord.Ctrl_CELLX:
cf = formatState.RowFormat.CurrentCellFormat();
cf.CellX = token.Parameter;
cf.IsPending = false;
break;
case RtfControlWord.Ctrl_RTLROW:
formatState.RowFormat.Dir = DirState.DirRTL;
break;
case RtfControlWord.Ctrl_LTRROW:
formatState.RowFormat.Dir = DirState.DirLTR;
break;
// Cell merging
case RtfControlWord.Ctrl_CLMGF:
// First cell in range of table cells to be merged.
cf = formatState.RowFormat.CurrentCellFormat();
cf.IsHMergeFirst = true;
break;
case RtfControlWord.Ctrl_CLMRG:
// Contents of this cell are merged with those of the preceding cell.
cf = formatState.RowFormat.CurrentCellFormat();
cf.IsHMerge = true;
break;
case RtfControlWord.Ctrl_CLVMGF:
// First cell in range of cells to be vertically merged.
cf = formatState.RowFormat.CurrentCellFormat();
cf.IsVMergeFirst = true;
break;
case RtfControlWord.Ctrl_CLVMRG:
// Contents of this cell are vertically merged with those of the preceding cell.
cf = formatState.RowFormat.CurrentCellFormat();
cf.IsVMerge = true;
break;
// General borders, not just tables
case RtfControlWord.Ctrl_BRDRL:
// Left paragraph border
ConverterState.CurrentBorder = formatState.ParaBorder.BorderLeft;
break;
case RtfControlWord.Ctrl_BRDRR:
// Right paragraph border
ConverterState.CurrentBorder = formatState.ParaBorder.BorderRight;
break;
case RtfControlWord.Ctrl_BRDRT:
// Top paragraph border
ConverterState.CurrentBorder = formatState.ParaBorder.BorderTop;
break;
case RtfControlWord.Ctrl_BRDRB:
// Bottom paragraph border
ConverterState.CurrentBorder = formatState.ParaBorder.BorderBottom;
break;
case RtfControlWord.Ctrl_BOX:
// All four borders
ConverterState.CurrentBorder = formatState.ParaBorder.BorderAll;
break;
case RtfControlWord.Ctrl_BRDRNIL:
// No borders
ConverterState.CurrentBorder = null;
break;
case RtfControlWord.Ctrl_BRSP:
// Space in twips between borders and paragraph
formatState.ParaBorder.Spacing = token.Parameter;
break;
case RtfControlWord.Ctrl_BRDRTBL:
// No cell borders
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderNone;
}
break;
case RtfControlWord.Ctrl_BRDRART:
// Art border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRBAR:
// Border on outside edge of page (treat as BRDRL for XAML)
break;
case RtfControlWord.Ctrl_BRDRBTW:
break;
case RtfControlWord.Ctrl_BRDRCF:
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.CF = token.Parameter;
}
break;
case RtfControlWord.Ctrl_BRDRDASH:
// Dashed border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRDASHD:
// Dash dot border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRDASHDD:
// Dot dot dash border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRDASHDOTSTR:
// Dash-dot border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRDASHSM:
// Small dash border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRDB:
// Double border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderDouble;
}
break;
case RtfControlWord.Ctrl_BRDRDOT:
// Dotted border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDREMBOSS:
// Emboss-style border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRENGRAVE:
// Engrave-style border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRFRAME:
// Frame-style border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRHAIR:
// Hairline border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRINSET:
// Inset border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDROUTSET:
// Outset border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRS:
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRSH:
// Shadow border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTH:
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderDouble;
}
break;
case RtfControlWord.Ctrl_BRDRTHTNLG:
// Thick-thin (large) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTHTNMG:
// Thick-thin (medium) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTHTNSG:
// Thick-thin-thin (thin) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHLG:
// Thin-thick (large) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHMG:
// Thick-thin-thin (medium) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHSG:
// Thick-thin-thin (small) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHTNLG:
// Thick-thin-thin (large) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHTNMG:
// Thin-thick-thin (medium) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTNTHTNSG:
// Thick-thin-thin (small) border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRTRIPLE:
// Triple border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRW:
// Border thickness
if (ConverterState.CurrentBorder != null)
{
// Note that propset does validation
ConverterState.CurrentBorder.Width = token.Parameter;
}
break;
case RtfControlWord.Ctrl_BRDRNONE:
// No borders
if (ConverterState.CurrentBorder != null)
{
// Note that propset does validation
ConverterState.CurrentBorder.SetDefaults();
}
break;
case RtfControlWord.Ctrl_BRDRWAVY:
// Wavy border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderSingle;
}
break;
case RtfControlWord.Ctrl_BRDRWAVYDB:
// Double border
if (ConverterState.CurrentBorder != null)
{
ConverterState.CurrentBorder.Type = BorderType.BorderDouble;
}
break;
}
}
internal void HandleFieldTokens(RtfToken token, FormatState formatState)
{
// Don't start processing fields in non-normal destinatations
FormatState fsCur = _converterState.PreviousTopFormatState(0);
FormatState fsOld = _converterState.PreviousTopFormatState(1);
if (fsCur == null || fsOld == null)
{
return;
}
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_FIELD:
// Process fields in normal content or nested fields
if (!fsOld.IsContentDestination || formatState.IsHidden)
{
return;
}
formatState.RtfDestination = RtfDestination.DestField;
break;
case RtfControlWord.Ctrl_FLDRSLT:
if (fsOld.RtfDestination != RtfDestination.DestField)
{
return;
}
formatState.RtfDestination = RtfDestination.DestFieldResult;
break;
case RtfControlWord.Ctrl_FLDPRIV:
if (fsOld.RtfDestination != RtfDestination.DestField)
{
return;
}
formatState.RtfDestination = RtfDestination.DestFieldPrivate;
break;
case RtfControlWord.Ctrl_FLDINST:
if (fsOld.RtfDestination != RtfDestination.DestField)
{
return;
}
formatState.RtfDestination = RtfDestination.DestFieldInstruction;
break;
default:
return;
}
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnf = new DocumentNode(DocumentNodeType.dnFieldBegin);
dnf.FormatState = new FormatState(formatState);
dnf.IsPending = false; // Field start mark should not impact other tags open/close behavior
dnf.IsTerminated = true;
dna.Push(dnf);
}
internal void HandleTableNesting(FormatState formatState)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
// If we're in a throw-away destination, just return.
if (!formatState.IsContentDestination || formatState.IsHidden)
{
return;
}
// Make sure proper number of tables are open to reflect this paragraphs nest level
int nTables = dna.CountOpenNodes(DocumentNodeType.dnTable);
int nLevel = (int)formatState.TableLevel;
// If we're not in a table, end early
if (nTables == nLevel && nTables == 0)
{
return;
}
if (nTables > nLevel)
{
DocumentNode dnPara = dna.Pop();
bool bInField = dna.FindUnmatched(DocumentNodeType.dnFieldBegin) >= 0;
while (nTables > nLevel)
{
int nOpen = dna.FindPending(DocumentNodeType.dnTable);
if (nOpen >= 0)
{
dna.CloseAt(nOpen);
if (!bInField)
{
dna.CoalesceChildren(_converterState, nOpen);
}
}
nTables--;
}
dna.Push(dnPara);
}
else
{
// Before opening the table, let's close any open lists. Word (RTF) allows somewhat
// arbitrary interleaving (because there's no explicit nesting), but when converting to
// XAML we have to choose an explicit nesting. We never create a table *inside* a list, so
// let's just terminate any open lists right now.
if (nTables < nLevel)
{
int nListAt = dna.FindPending(DocumentNodeType.dnList);
if (nListAt >= 0)
{
// I want the currently pending paragraph to be part of the table, not part of the list
// I'm going to close. So I temporarily pop it off while closing off the list and then
// push it back on before inserting the table(s).
DocumentNode dnPara = dna.Pop();
while (nListAt >= 0)
{
dna.CloseAt(nListAt);
nListAt = dna.FindPending(DocumentNodeType.dnList);
}
dna.Push(dnPara);
}
}
// Ensure sufficient tables are open - this may be our first indication
// Insert the table nodes right before the current paragraph.
Debug.Assert(dna.Count > 0 && dna.EntryAt(dna.Count - 1).Type == DocumentNodeType.dnParagraph);
int nInsertAt = dna.Count - 1;
// Ensure row is open
int nTable = dna.FindPending(DocumentNodeType.dnTable);
if (nTable >= 0)
{
int nRow = dna.FindPending(DocumentNodeType.dnRow, nTable);
if (nRow == -1)
{
DocumentNode dnRow = new DocumentNode(DocumentNodeType.dnRow);
dna.InsertNode(nInsertAt++, dnRow);
nRow = nInsertAt - 1;
}
int nCell = dna.FindPending(DocumentNodeType.dnCell, nRow);
if (nCell == -1)
{
DocumentNode dnCell = new DocumentNode(DocumentNodeType.dnCell);
dna.InsertNode(nInsertAt, dnCell);
}
}
nInsertAt = dna.Count - 1;
while (nTables < nLevel)
{
DocumentNode dnTable = new DocumentNode(DocumentNodeType.dnTable);
DocumentNode dnTableBody = new DocumentNode(DocumentNodeType.dnTableBody);
DocumentNode dnRow = new DocumentNode(DocumentNodeType.dnRow);
DocumentNode dnCell = new DocumentNode(DocumentNodeType.dnCell);
dna.InsertNode(nInsertAt, dnCell);
dna.InsertNode(nInsertAt, dnRow);
dna.InsertNode(nInsertAt, dnTableBody);
dna.InsertNode(nInsertAt, dnTable);
nTables++;
}
}
dna.AssertTreeSemanticInvariants();
}
internal MarkerList GetMarkerStylesOfParagraph(MarkerList mlHave, FormatState fs, bool bMarkerPresent)
{
MarkerList mlWant = new MarkerList();
long nVirtualListLevel = fs.ListLevel;
long nStartIndexOverride = -1;
// No list?
if (nVirtualListLevel < 1)
{
return mlWant;
}
// Use currently open list styles for all levels below requested one
for (int i = 0; i < mlHave.Count; i++)
if (mlHave.EntryAt(i).VirtualListLevel < nVirtualListLevel || fs.IsContinue)
{
MarkerListEntry mle = mlHave.EntryAt(i);
mlWant.AddEntry(mle.Marker, mle.ILS, -1, mle.StartIndexDefault, mle.VirtualListLevel);
}
else
{
break;
}
// If I'm a continuation paragraph, I'm done.
if (fs.IsContinue)
{
return mlWant;
}
// Now determine the list style for the list level I'm going to add.
ListOverrideTable lot = _converterState.ListOverrideTable;
ListOverride lo = lot.FindEntry((int)fs.ILS);
if (lo != null)
{
ListLevelTable levels = lo.Levels;
if (levels == null || levels.Count == 0)
{
ListTableEntry lte = _converterState.ListTable.FindEntry(lo.ID);
if (lte != null)
{
levels = lte.Levels;
}
// Did the list override specify a start index?
if (lo.StartIndex > 0)
{
nStartIndexOverride = lo.StartIndex;
}
}
if (levels != null)
{
ListLevel listLevel = levels.EntryAt((int)nVirtualListLevel - 1);
if (listLevel != null)
{
// If there was a marker present, we ignore the "Hidden" style in the list table.
MarkerStyle ms = listLevel.Marker;
if (ms == MarkerStyle.MarkerHidden && bMarkerPresent)
{
ms = MarkerStyle.MarkerBullet;
}
mlWant.AddEntry(ms, fs.ILS, nStartIndexOverride, listLevel.StartIndex, nVirtualListLevel);
return mlWant;
}
}
}
// If there wasn't a list definition, use the marker type in the formatstate.
mlWant.AddEntry(fs.Marker, fs.ILS, nStartIndexOverride, fs.StartIndex, nVirtualListLevel);
return mlWant;
}
internal void HandleListNesting(FormatState formatState)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnPara = dna.EntryAt(dna.Count - 1);
bool bMarkerPresent = _converterState.IsMarkerPresent;
// Test if we encountered list text
if (_converterState.IsMarkerPresent)
{
_converterState.IsMarkerPresent = false;
}
// If we're in a throw-away destination, just return.
if (!formatState.IsContentDestination || formatState.IsHidden)
{
return;
}
// Treat no marker text present as a continuation paragraph
if (!bMarkerPresent && formatState.ListLevel > 0)
{
// Allocate a new one here so that this change doesn't propagate beyond here.
formatState = new FormatState(formatState);
// If the only thing making this look like a list is the ILVL property, just clear it.
if (formatState.ILVL > 0 && formatState.ILS < 0)
{
formatState.ILVL = 0;
}
else // otherwise treat this as a continuation paragraph.
{
formatState.IsContinue = true;
}
}
// Make sure proper number of lists are open to reflect this paragraphs nest level
MarkerList mlHave = dna.GetOpenMarkerStyles();
MarkerList mlWant = GetMarkerStylesOfParagraph(mlHave, formatState, bMarkerPresent);
int nLists = mlHave.Count;
int nLevel = mlWant.Count;
// If we're not in a list end early
if (nLists == nLevel && nLists == 0)
{
return;
}
// If we're not in a list and marked as a continuation, ignore this - anomaly.
if (nLists == 0 && nLevel == 1 && formatState.IsContinue)
{
return;
}
// Propagate noticing that the specified text was empty.
if (_converterState.IsMarkerWhiteSpace)
{
_converterState.IsMarkerWhiteSpace = false;
if (nLevel > 0)
{
MarkerListEntry entry = mlWant.EntryAt(nLevel - 1);
entry.Marker = MarkerStyle.MarkerHidden;
}
}
// The ones we have are only "good" if the styles match what we want.
int nMatch = GetMatchedMarkList(formatState, mlHave, mlWant);
// If none match, we might do better by extending some previously open list.
if (nMatch == 0)
{
MarkerList mlCouldHave = dna.GetLastMarkerStyles(mlHave, mlWant);
MarkerList mlCouldWant = GetMarkerStylesOfParagraph(mlCouldHave, formatState, bMarkerPresent);
nMatch = GetMatchedMarkList(formatState, mlCouldHave, mlCouldWant);
// If I would re-open a previous list but close some set of them and then reopen another,
// this is unlikely to be what the user intended. Instead, don't extend the previous list.
// (RtfConverter:Spacing between left most edge and marker is incorrect on last list item (RTF-XAML))
if (nMatch < mlCouldHave.Count && mlCouldWant.Count > nMatch)
{
nMatch = 0;
}
if (nMatch > 0)
{
mlHave = mlCouldHave;
mlWant = mlCouldWant;
dna.OpenLastList();
}
}
// Ensure list and listitem
EnsureListAndListItem(formatState, dna, mlHave, mlWant, nMatch);
// To get better results in XAML output, clear the FI for the first para in a list item.
if (dna.Count > 1 && dna.EntryAt(dna.Count - 2).Type == DocumentNodeType.dnListItem)
{
Debug.Assert(!dnPara.IsTerminated);
dnPara.FormatState.FI = 0;
}
dna.AssertTreeSemanticInvariants();
}
internal void HandleCodePageTokens(RtfToken token, FormatState formatState)
{
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_ANSI:
// ANSI apparently means specifically 1252, not ACP. That makes a lot more sense...
//_converterState.CodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
_converterState.CodePage = 1252;
_lexer.CodePage = _converterState.CodePage;
break;
case RtfControlWord.Ctrl_MAC:
_converterState.CodePage = 10000;
_lexer.CodePage = _converterState.CodePage;
break;
case RtfControlWord.Ctrl_PC:
_converterState.CodePage = 437;
_lexer.CodePage = _converterState.CodePage;
break;
case RtfControlWord.Ctrl_PCA:
_converterState.CodePage = 850;
_lexer.CodePage = _converterState.CodePage;
break;
case RtfControlWord.Ctrl_UPR:
// We discard this ansi representation - \ud will then switch back to current.
formatState.RtfDestination = RtfDestination.DestUPR;
break;
case RtfControlWord.Ctrl_U:
ProcessText(char.ToString((char)token.Parameter));
break;
case RtfControlWord.Ctrl_UD:
{
// We are parsing: {\upr ansi stuff{\*\ud unicode stuff }}
// When we encountered the UPR we set state to a throwaway destination (DestUPR).
// The nested group pushed a new format state but that now has DestUnknown because of this.
// Now that we encountered the \ud destination, lets push back the original destination.
FormatState previous = _converterState.PreviousTopFormatState(1);
FormatState previousPrevious = _converterState.PreviousTopFormatState(2);
if (previous != null && previousPrevious != null)
{
if (formatState.RtfDestination == RtfDestination.DestUPR && previous.RtfDestination == RtfDestination.DestUnknown)
{
formatState.RtfDestination = previousPrevious.RtfDestination;
}
}
}
break;
case RtfControlWord.Ctrl_UC:
formatState.UnicodeSkip = (int)token.Parameter;
break;
}
}
internal void ProcessFieldText(RtfToken token)
{
switch (_converterState.TopFormatState.RtfDestination)
{
case RtfDestination.DestField:
break;
case RtfDestination.DestFieldInstruction:
// Gather up for later processing
HandleNormalText(token.Text, _converterState.TopFormatState);
break;
case RtfDestination.DestFieldPrivate:
// Discard
break;
case RtfDestination.DestFieldResult:
HandleNormalText(token.Text, _converterState.TopFormatState);
break;
}
}
internal void ProcessFontTableText(RtfToken token)
{
string tokenName = token.Text;
// Strip line endings
tokenName = tokenName.Replace("\r\n", "");
// Strip trailing semi-colon
tokenName = tokenName.Replace(";", "");
FontTableEntry entry = _converterState.FontTable.CurrentEntry;
if (entry != null && tokenName.Length > 0 && !entry.IsNameSealed)
{
// If name not yet specified, just set it
if (entry.Name == null)
{
entry.Name = tokenName;
}
else // Otherwise, append it
{
entry.Name += tokenName;
}
}
}
internal void HandleFontTableTokens(RtfToken token)
{
FontTableEntry entry = _converterState.FontTable.CurrentEntry;
FormatState formatState = _converterState.TopFormatState;
if (entry != null)
{
switch (token.RtfControlWordInfo.Control)
{
case RtfControlWord.Ctrl_FCHARSET:
// Set the codepage to the font table entry
entry.CodePageFromCharSet = (int)token.Parameter;
// Also set lexer code page
if (entry.CodePage == -1)
{
formatState.CodePage = _converterState.CodePage;
}
else
{
formatState.CodePage = entry.CodePage;
}
_lexer.CodePage = formatState.CodePage;
break;
}
}
}
internal void ProcessColorTableText(RtfToken token)
{
// This is just a separator for color table entries
_converterState.ColorTable.FinishColor();
}
internal void ProcessText(string text)
{
FormatState fs = _converterState.TopFormatState;
if (fs.IsContentDestination && !fs.IsHidden && text != string.Empty)
{
HandleNormalTextRaw(text, fs);
}
}
internal void HandleNormalText(string text, FormatState formatState)
{
// Normal CRLF's are eaten by the RTF lexer. Any ones that have slipped through here
// were either escaped or hex-encoded and should be treated as a linebreak.
int nStart = 0;
while (nStart < text.Length)
{
int nEnd = nStart;
while (nEnd < text.Length)
{
if (text[nEnd] == 0x0d || text[nEnd] == 0x0a)
{
break;
}
nEnd++;
}
// Handle text before newline
if (nStart == 0 && nEnd == text.Length)
{
HandleNormalTextRaw(text, formatState);
}
else if (nEnd > nStart)
{
string subtext = text.Substring(nStart, nEnd - nStart);
HandleNormalTextRaw(subtext, formatState);
}
// Handle newlines
while (nEnd < text.Length && (text[nEnd] == 0x0d || text[nEnd] == 0x0a))
{
ProcessNormalHardLine(formatState);
if (nEnd + 1 < text.Length && text[nEnd] == 0x0d && text[nEnd] == 0x0a)
{
nEnd += 2;
}
else
{
nEnd += 1;
}
}
nStart = nEnd;
}
}
internal void HandleNormalTextRaw(string text, FormatState formatState)
{
DocumentNodeArray dna = _converterState.DocumentNodeArray;
DocumentNode dnTop = dna.Top;
// See if I can just append the text content if the format is the same.
if (dnTop != null && (dnTop.Type == DocumentNodeType.dnText))
{
// If the format is not equal, close this text element and we'll open a new one.
if (!dnTop.FormatState.IsEqual(formatState))
{
dna.CloseAt(dna.Count - 1);
dnTop = null;
}
}
// OK, create a text node if necessary
if (dnTop == null || dnTop.Type != DocumentNodeType.dnText)
{
dnTop = new DocumentNode(DocumentNodeType.dnText);
dnTop.FormatState = new FormatState(formatState);
dna.Push(dnTop);
}
Debug.Assert(!dnTop.IsTerminated);
dnTop.AppendXamlEncoded(text);
dnTop.IsPending = false;
}
internal void ProcessNormalHardLine(FormatState formatState)
{
// Close out pending text nodes
DocumentNodeArray dna = _converterState.DocumentNodeArray;
if (dna.TestTop(DocumentNodeType.dnText))
{
dna.CloseAt(dna.Count - 1);
}
DocumentNode documentNode = new DocumentNode(DocumentNodeType.dnLineBreak);
documentNode.FormatState = new FormatState(formatState);
dna.Push(documentNode);
dna.CloseAt(dna.Count - 1);
dna.CoalesceChildren(_converterState, dna.Count - 1);
}
internal void ProcessHardLine(RtfToken token, FormatState formatState)
{
switch (_converterState.TopFormatState.RtfDestination)
{
case RtfDestination.DestNormal:
case RtfDestination.DestFieldResult:
case RtfDestination.DestShapeResult:
case RtfDestination.DestListText:
ProcessNormalHardLine(formatState);
break;
case RtfDestination.DestFontTable:
case RtfDestination.DestFontName:
break;
case RtfDestination.DestColorTable:
break;
case RtfDestination.DestField:
break;
case RtfDestination.DestFieldInstruction:
case RtfDestination.DestFieldPrivate:
ProcessNormalHardLine(formatState);
break;
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private void SetTokenTextWithControlCharacter(RtfToken token)
{
switch (token.Text[0])
{
case '~':
// NBSP
token.Text = new string('\xA0', 1); // Unicode NBSP
break;
case '-':
// Optional hypen (not really in input)
token.Text = string.Empty;
break;
case ':':
// Sub-entry in index - leave as literal
break;
case '_':
// Non-breaking hypen - convert to real hypen
token.Text = new string('\x2011', 1);
break;
case '|':
// Formula character - leave as literal (or toss?)
break;
// Escaped lexically special RTF characters - leave as literal text
case '\\':
case '{':
case '}':
break;
}
}
private int GetMatchedMarkList(FormatState formatState, MarkerList mlHave, MarkerList mlWant)
{
// The ones we have are only "good" if the styles match what we want.
int nMatch = 0;
for (; nMatch < mlHave.Count && nMatch < mlWant.Count; nMatch++)
{
if (!formatState.IsContinue)
{
MarkerListEntry eHave = mlHave.EntryAt(nMatch);
MarkerListEntry eWant = mlWant.EntryAt(nMatch);
if (eHave.Marker != eWant.Marker
|| eHave.ILS != eWant.ILS
|| eHave.StartIndexDefault != eWant.StartIndexDefault
|| eWant.StartIndexOverride >= 0)
{
break;
}
}
}
return nMatch;
}
private void EnsureListAndListItem(FormatState formatState, DocumentNodeArray dna, MarkerList mlHave, MarkerList mlWant, int nMatch)
{
int nInsertAt;
bool added = false;
int nLists = mlHave.Count;
int nLevel = mlWant.Count;
// Close any open lists that don't match the ones we want.
bool bInField = dna.FindUnmatched(DocumentNodeType.dnFieldBegin) >= 0;
if (nLists > nMatch)
{
DocumentNode documentNodePara = dna.Pop();
while (nLists > nMatch)
{
int nOpen = dna.FindPending(DocumentNodeType.dnList);
if (nOpen >= 0)
{
dna.CloseAt(nOpen);
// Only coalesce if this is a top-level list. Otherwise I want to get
// the full structure to use for margin fixups so I delay coalescing.
// No, don't coalesce since a later list may need to get merged with this one.
// if (!bInField && dna.FindPending(DocumentNodeType.dnList) < 0)
// dna.CoalesceChildren(_converterState, nOpen);
}
nLists--;
mlHave.RemoveRange(mlHave.Count - 1, 1);
}
dna.Push(documentNodePara);
}
if (nLists < nLevel)
{
// Multiple immediately nested lists are handled poorly in Avalon and are usually an indication
// of bad input from Word (or some other word processor). Clip the number of lists we'll create here.
if (nLevel != nLists + 1)
{
// I'm going to truncate, but make the list I create here of the specific type at this level.
if (nLevel <= mlWant.Count)
{
mlWant[nLists] = mlWant[mlWant.Count - 1];
}
nLevel = nLists + 1;
}
// Ensure sufficient lists are open - this may be our first indication
// Insert the list nodes right before the current paragraph
nInsertAt = dna.Count - 1;
while (nLists < nLevel)
{
added = true;
DocumentNode dnList = new DocumentNode(DocumentNodeType.dnList);
DocumentNode dnLI = new DocumentNode(DocumentNodeType.dnListItem);
dna.InsertNode(nInsertAt, dnLI);
dna.InsertNode(nInsertAt, dnList);
// Set the list properties
MarkerListEntry mle = mlWant.EntryAt(nLists);
dnList.FormatState.Marker = mle.Marker;
dnList.FormatState.StartIndex = mle.StartIndexToUse;
dnList.FormatState.StartIndexDefault = mle.StartIndexDefault;
dnList.VirtualListLevel = mle.VirtualListLevel;
dnList.FormatState.ILS = mle.ILS;
nLists++;
}
}
// Ensure listitem is open
nInsertAt = dna.Count - 1;
int nList = dna.FindPending(DocumentNodeType.dnList);
if (nList >= 0)
{
int nLI = dna.FindPending(DocumentNodeType.dnListItem, nList);
if (nLI >= 0 && !added && !formatState.IsContinue)
{
DocumentNode documentNodePara = dna.Pop();
dna.CloseAt(nLI);
// Don't coalesce - I may need to do margin fixup.
// dna.CoalesceChildren(_converterState, nLI);
dna.Push(documentNodePara);
nLI = -1;
nInsertAt = dna.Count - 1;
}
if (nLI == -1)
{
DocumentNode dnLI = new DocumentNode(DocumentNodeType.dnListItem);
dna.InsertNode(nInsertAt, dnLI);
}
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Enum
//
//------------------------------------------------------
#region Private Enum
private enum EncodeType
{
Ansi,
Unicode,
ShiftJis
}
#endregion Private Enum
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private byte[] _rtfBytes;
private StringBuilder _outerXamlBuilder;
private RtfToXamlLexer _lexer;
private ConverterState _converterState;
private bool _bForceParagraph;
// WpfPayload package that containing the image for the specified Xaml
private WpfPayload _wpfPayload;
// Rtf image count that is the unique image id which is on WpfPayload
private int _imageCount;
#endregion Private Fields
//------------------------------------------------------
//
// Private Const
//
//------------------------------------------------------
#region Private Const
private const int MAX_GROUP_DEPTH = 32;
#endregion Private Const
}
}
|