private void WriteUIContainerChild(DocumentNode documentNode) { _rtfBuilder.Append("{"); DocumentNodeArray dna = _converterState.DocumentNodeArray; // Write child contents int nStart = documentNode.Index + 1; int nEnd = documentNode.Index + documentNode.ChildCount; for (; nStart <= nEnd; nStart++) { DocumentNode documentNodeChild = dna.EntryAt(nStart); // Ignore non-direct children - they get written out by their parent if (documentNodeChild.Parent == documentNode && documentNodeChild.Type == DocumentNodeType.dnImage) { // Write image control and image hex data to the rtf content WriteImage(documentNodeChild); } } if (documentNode.Type == DocumentNodeType.dnBlockUIContainer) { _rtfBuilder.Append("\\par"); } // Close Section writing _rtfBuilder.Append("}"); _rtfBuilder.Append("\r\n"); }
private void ConvertSymbolCharValueToText(DocumentNode dn, int nChar, EncodeType encodeType) { switch (encodeType) { case EncodeType.Unicode: if (nChar < 0xFFFF) { char[] unicodeChar = new char[1]; unicodeChar[0] = (char)nChar; dn.AppendXamlEncoded(new string(unicodeChar)); } break; case EncodeType.ShiftJis: if (nChar < 0xFFFF) { // NB: How to interpret this numeric value as Shift-JIS? Encoding ec = Encoding.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 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 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; }
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; } string ptString = instr.Substring(iStart, i - iStart); // Now convert number part bool ret = true; double d = 0f; try { d = System.Convert.ToDouble(ptString, 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; } }
//------------------------------------------------------ // // 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> // 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; }
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 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 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 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 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 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; } }
private void WriteSection(DocumentNode dnThis) { int nStart = dnThis.Index + 1; int nEnd = dnThis.Index + dnThis.ChildCount; int nAt; FormatState fsThis = dnThis.FormatState; FormatState fsParent = dnThis.Parent != null ? dnThis.Parent.FormatState : FormatState.EmptyFormatState; DocumentNodeArray dna = _converterState.DocumentNodeArray; _rtfBuilder.Append("{"); // CultureInfo if (fsThis.Lang != fsParent.Lang && fsThis.Lang > 0) { _rtfBuilder.Append("\\lang"); _rtfBuilder.Append(fsThis.Lang.ToString(CultureInfo.InvariantCulture)); } // FlowDirection if (fsThis.DirPara == DirState.DirRTL) { _rtfBuilder.Append("\\rtlpar"); } // Write the font information if (WriteParagraphFontInfo(dnThis, fsThis, fsParent)) { _rtfBuilder.Append(" "); } // Foreground if (fsThis.CF != fsParent.CF) { _rtfBuilder.Append("\\cf"); _rtfBuilder.Append(fsThis.CF.ToString(CultureInfo.InvariantCulture)); } // TextAlignment switch (fsThis.HAlign) { case HAlign.AlignLeft: if (fsThis.DirPara != DirState.DirRTL) { _rtfBuilder.Append("\\ql"); } else { _rtfBuilder.Append("\\qr"); } break; case HAlign.AlignRight: if (fsThis.DirPara != DirState.DirRTL) { _rtfBuilder.Append("\\qr"); } else { _rtfBuilder.Append("\\ql"); } break; case HAlign.AlignCenter: _rtfBuilder.Append("\\qc"); break; case HAlign.AlignJustify: _rtfBuilder.Append("\\qj"); break; } // LineHeight if (fsThis.SL != 0) { _rtfBuilder.Append("\\sl"); _rtfBuilder.Append(fsThis.SL.ToString(CultureInfo.InvariantCulture)); _rtfBuilder.Append("\\slmult0"); } // Now write out the direct children. for (nAt = nStart; nAt <= nEnd; nAt++) { DocumentNode dnChild = dna.EntryAt(nAt); // Ignore non-direct children - they get written out by their parent if (dnChild.Parent == dnThis) { WriteStructure(dnChild); } } // Close Section writing _rtfBuilder.Append("}"); _rtfBuilder.Append("\r\n"); }
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 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 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(); }
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); } } }
// <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; } } } }
internal bool IsAncestorOf(DocumentNode documentNode) { int parentIndex = Index; int parentLastChild = Index + ChildCount; return documentNode.Index > parentIndex && documentNode.Index <= parentLastChild; }
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; } } } }
internal void Push(DocumentNode documentNode) { InsertNode(Count, documentNode); }
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; }
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 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 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(); } }
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; }
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 InsertOpenNode(DocumentNode dn) { CullOpen(); int i = Count; for (; i > 0; i--) { if (dn.Index > EntryAt(i - 1).Index) { break; } } Insert(i, dn); }
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); }
private void WriteInlineChild(DocumentNode documentNode) { // Handle empty nodes first if (documentNode.IsEmptyNode) { WriteEmptyChild(documentNode); return; } DocumentNodeArray dna = _converterState.DocumentNodeArray; FormatState fsThis = documentNode.FormatState; FormatState fsParent = documentNode.Parent != null ? documentNode.Parent.FormatState : FormatState.EmptyFormatState; bool outFont = fsThis.Font != fsParent.Font; bool outBold = fsThis.Bold != fsParent.Bold; bool outItalic = fsThis.Italic != fsParent.Italic; bool outUL = fsThis.UL != fsParent.UL; bool outFontSize = fsThis.FontSize != fsParent.FontSize; bool outCF = fsThis.CF != fsParent.CF; bool outCB = fsThis.CB != fsParent.CB; bool outS = fsThis.Strike != fsParent.Strike; bool outSuper = fsThis.Super != fsParent.Super; bool outSub = fsThis.Sub != fsParent.Sub; bool outLang = fsThis.Lang != fsParent.Lang && fsThis.Lang > 0; bool outDir = fsThis.DirChar != DirState.DirDefault && (documentNode.Parent == null || !documentNode.Parent.IsInline || fsThis.Lang != fsParent.Lang); bool outAny = outFont || outBold || outItalic || outUL || outLang || outDir || outFontSize || outCF || outCB || outS || outSuper || outSub; // Start a context so any properties only apply here if (outAny) { _rtfBuilder.Append("{"); } // Write properties if (outLang) { _rtfBuilder.Append("\\lang"); _rtfBuilder.Append(fsThis.Lang.ToString(CultureInfo.InvariantCulture)); } if (outFont) { _rtfBuilder.Append("\\loch"); _rtfBuilder.Append("\\f"); _rtfBuilder.Append(fsThis.Font.ToString(CultureInfo.InvariantCulture)); } if (outBold) { if (fsThis.Bold) { _rtfBuilder.Append("\\b"); } else { _rtfBuilder.Append("\\b0"); } } if (outItalic) { if (fsThis.Italic) { _rtfBuilder.Append("\\i"); } else { _rtfBuilder.Append("\\i0"); } } if (outUL) { if (fsThis.UL != ULState.ULNone) { _rtfBuilder.Append("\\ul"); } else { _rtfBuilder.Append("\\ul0"); } } if (outS) { if (fsThis.Strike != StrikeState.StrikeNone) { _rtfBuilder.Append("\\strike"); } else { _rtfBuilder.Append("\\strike0"); } } if (outFontSize) { _rtfBuilder.Append("\\fs"); _rtfBuilder.Append(fsThis.FontSize.ToString(CultureInfo.InvariantCulture)); } if (outCF) { _rtfBuilder.Append("\\cf"); _rtfBuilder.Append(fsThis.CF.ToString(CultureInfo.InvariantCulture)); } if (outCB) { _rtfBuilder.Append("\\highlight"); _rtfBuilder.Append(fsThis.CB.ToString(CultureInfo.InvariantCulture)); } if (outSuper) { if (fsThis.Super) { _rtfBuilder.Append("\\super"); } else { _rtfBuilder.Append("\\super0"); } } if (outSub) { if (fsThis.Sub) { _rtfBuilder.Append("\\sub"); } else { _rtfBuilder.Append("\\sub0"); } } if (outDir) { if (fsThis.DirChar == DirState.DirLTR) { _rtfBuilder.Append("\\ltrch"); } else { _rtfBuilder.Append("\\rtlch"); } } // Ensure space delimiter after control word if (outAny) { _rtfBuilder.Append(" "); } // Write contents here if (documentNode.Type == DocumentNodeType.dnHyperlink && !string.IsNullOrEmpty(documentNode.NavigateUri)) { _rtfBuilder.Append("{\\field{\\*\\fldinst { HYPERLINK \""); // Add the additional backslash which rtf expected for (int i = 0; i < documentNode.NavigateUri.Length; i++) { if (documentNode.NavigateUri[i] == '\\') { _rtfBuilder.Append("\\\\"); } else { _rtfBuilder.Append(documentNode.NavigateUri[i]); } } _rtfBuilder.Append("\" }}{\\fldrslt {"); } else { _rtfBuilder.Append(documentNode.Content); } if (documentNode.Type == DocumentNodeType.dnImage) { // Write image control and image hex data to the rtf content WriteImage(documentNode); } // Write child contents int nStart = documentNode.Index + 1; int nEnd = documentNode.Index + documentNode.ChildCount; for (; nStart <= nEnd; nStart++) { DocumentNode documentNodeChild = dna.EntryAt(nStart); // Ignore non-direct children - they get written out by their parent if (documentNodeChild.Parent == documentNode) { WriteInlineChild(documentNodeChild); } } // Terminate contents here if (documentNode.Type == DocumentNodeType.dnHyperlink && !string.IsNullOrEmpty(documentNode.NavigateUri)) { _rtfBuilder.Append("}}}"); } // End context if (outAny) { _rtfBuilder.Append("}"); } }