/// <summary> /// Insert paragraph break at the End position of a range. /// It only affects specified position - not a whole range. /// So it is essentially TextContainer-level (low-level) operation. /// </summary> /// <param name="position"> /// Position at which the content should be split into two paragraphs. /// After the operation breakPosition moved into a beginning of the /// second paragraph after all opening tags created by splitting /// (this position may be not-normalized though if there are some /// other opening formatting tags following the position - this may /// be important for reading from xml when pasting point was before /// some opening formatting tags but after non-whitespace characters). /// </param> /// <param name="moveIntoSecondParagraph"> /// True means that resulting TextPointer must be moved into the second paragraph. /// False means that resulting pointer remains in a non-normalized position /// between two paragraphs (or list items). /// </param> /// <remarks> /// This function could be implemented from TextContainer class. /// </remarks> /// <returns> /// If position passed was in paragraph content, returns a TextPointer /// at an ContentStart of the second paragraph. /// If position passed was at a structural boundary (specifically table row end, /// block ui container start/end or before first table in a collection of blocks), /// then an implicit paragraph is inserted at the boundary and a position at its /// ContentStart is returned. /// </returns> internal static TextPointer InsertParagraphBreak(TextPointer position, bool moveIntoSecondParagraph) { Invariant.Assert(position.TextContainer.Parent == null || TextSchema.IsValidChildOfContainer(position.TextContainer.Parent.GetType(), typeof(Paragraph))); bool structuralBoundaryCrossed = TextPointerBase.IsAtRowEnd(position) || TextPointerBase.IsBeforeFirstTable(position) || TextPointerBase.IsInBlockUIContainer(position); if (position.Paragraph == null) { // Ensure insertion position, in case original position is not in text content. position = TextRangeEditTables.EnsureInsertionPosition(position); } Inline ancestor = position.GetNonMergeableInlineAncestor(); if (ancestor != null) { Invariant.Assert(TextPointerBase.IsPositionAtNonMergeableInlineBoundary(position), "Position must be at hyperlink boundary!"); // If position is at a hyperlink boundary, move outside hyperlink element scope // so that we can successfuly split formatting elements upto paragraph ancestor. position = position.IsAtNonMergeableInlineStart ? ancestor.ElementStart : ancestor.ElementEnd; } Paragraph paragraph = position.Paragraph; if (paragraph == null) { // At this point, we expect we're working in a fragment of Inlines only. Invariant.Assert(position.TextContainer.Parent == null); // Add a parent Paragraph to split. paragraph = new Paragraph(); paragraph.Reposition(position.DocumentStart, position.DocumentEnd); } if (structuralBoundaryCrossed) { // In case structural boundary was crossed, an implicit paragraph was inserted in EnsureInsertionPosition. // No need to insert another paragraph break. return position; } TextPointer breakPosition = position; // Split all inline elements up to this paragraph breakPosition = SplitFormattingElements(breakPosition, /*keepEmptyFormatting:*/true); Invariant.Assert(breakPosition.Parent == paragraph, "breakPosition must be in paragraph scope after splitting formatting elements"); // Decide whether we need to split ListItem around this paragraph (if any). // We are splitting a list item if this paragraph is the only paragraph in a list item. // Otherwise we simply produce new paragraphs within the same list item. bool needToSplitListItem = TextPointerBase.GetImmediateListItem(paragraph.ContentStart) != null; breakPosition = SplitElement(breakPosition); // Also split ListItem (if any) if (needToSplitListItem) { Invariant.Assert(breakPosition.Parent is ListItem, "breakPosition must be in ListItem scope"); breakPosition = SplitElement(breakPosition); } if (moveIntoSecondParagraph) { // Move breakPosition inside of the second paragraph while (!(breakPosition.Parent is Paragraph) && breakPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { breakPosition = breakPosition.GetNextContextPosition(LogicalDirection.Forward); } // Normalize with forward gravity breakPosition = breakPosition.GetInsertionPosition(LogicalDirection.Forward); } return breakPosition; }
// Helper for EnsureInsertionPosition, inserts a Paragraph element with a single Run at this position. private static TextPointer CreateImplicitParagraph(TextPointer position) { TextPointer insertionPosition; Paragraph implicitParagraph = new Paragraph(); implicitParagraph.Reposition(position, position); Run implicitRun = Run.CreateImplicitRun(implicitParagraph); implicitParagraph.Inlines.Add(implicitRun); insertionPosition = implicitRun.ContentStart.GetFrozenPointer(position.LogicalDirection); // return a position with the same orientation inside a Run return insertionPosition; }