|
// 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: Hosts and formats text with advanced document features.
//
using System.Collections;
using System.ComponentModel;
using System.Windows.Automation.Peers; // AutomationPeer
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using MS.Internal; // DoubleUtil
using MS.Internal.Documents;
using MS.Internal.PtsHost;
using MS.Internal.PtsHost.UnsafeNativeMethods; // PTS restrictions
using MS.Internal.Telemetry.PresentationFramework;
using MS.Internal.Text;
namespace System.Windows.Documents
{
/// <summary>
/// FlowDocument is the paginating container for text content.
/// </summary>
/// <remarks>
/// <para>
/// FlowDocument does not derive from Visual, and therefore cannot
/// be added as a direct child of UIElements such as Grid.
/// In order to be displayed, FlowDocument must be viewed within a FlowDocumentPageViewer,
/// DocumentViewerBase-derived class, or a custom paginated viewer that utilizes the
/// IDocumentPaginatorSource API.
/// It also can be viewed and edited within RichTextBox control,
/// available as it child in xaml markup or via Document property in api.
/// </para>
/// <para>
/// FlowDocument's content allows elements in some particular hierarchical structure
/// specified by Flow Content Schema. The Flow Cotent Schema includes a variety
/// of element classes that you can use to create rich formatted and structured text content.
/// </para>
/// <para>
/// Classes in Flow Content Schema belong to several categories: "Block content",
/// "Inline Content", "Embedded UIElements".
/// </para>
/// <para>
/// Top-level children of Flow Document must be one of <see cref="Block"/>-derived classes:
/// <see cref="Paragraph"/>, <see cref="Section"/>, <see cref="List"/>, <see cref="Table"/>.
/// - Block-level of flow content schema.
/// </para>
/// <para>
/// Each of block elements has specific schema, only <see cref="Paragraph"/> allowing
/// inline content - elements deived from <see cref="Inline"/> class:
/// <see cref="Run"/>, <see cref="Span"/>, <see cref="InlineUIContainer"/>, <see cref="Floater"/>, <see cref="Figure"/>.
/// </para>
/// <para>
/// Only <see cref="Run"/> element can contain text directly. All other elements can only contain
/// elements specified by Flow Schema.
/// </para>
/// </remarks>
[Localizability(LocalizationCategory.Inherit, Readability = Readability.Unreadable)]
[ContentProperty("Blocks")]
public class FlowDocument : FrameworkContentElement, IDocumentPaginatorSource, IServiceProvider, IAddChild
{
static private readonly Type _typeofThis = typeof(FlowDocument);
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
/// <summary>
/// Static constructor. Registers metadata for its properties.
/// </summary>
static FlowDocument()
{
PropertyChangedCallback typographyChanged = new PropertyChangedCallback(OnTypographyChanged);
// Registering typography properties metadata
DependencyProperty[] typographyProperties = Typography.TypographyPropertiesList;
for (int i = 0; i < typographyProperties.Length; i++)
{
typographyProperties[i].OverrideMetadata(_typeofThis, new FrameworkPropertyMetadata(typographyChanged));
}
DefaultStyleKeyProperty.OverrideMetadata(_typeofThis, new FrameworkPropertyMetadata(_typeofThis));
FocusableProperty.OverrideMetadata(_typeofThis, new FrameworkPropertyMetadata(true));
ControlsTraceLogger.AddControl(TelemetryControls.FlowDocument);
}
/// <summary>
/// FlowDocument constructor.
/// </summary>
public FlowDocument()
: base()
{
Initialize(null); // null means to create its own TextContainer
}
/// <summary>
/// Initialized the new instance of a FlowDocument specifying a Block added
/// as its first child.
/// </summary>
/// <param name="block">
/// Block added as a first initial child of the FlowDocument.
/// </param>
public FlowDocument(Block block)
: base()
{
Initialize(null); // null means to create its own TextContainer
ArgumentNullException.ThrowIfNull(block);
this.Blocks.Add(block);
}
/// <summary>
/// FlowDocument constructor with TextContainer.
/// </summary>
internal FlowDocument(TextContainer textContainer)
: base()
{
Initialize(textContainer);
}
#endregion Constructors
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
/// <value>
/// Collection of Blocks contained in this element
/// </value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public BlockCollection Blocks
{
get
{
return new BlockCollection(this, /*isOwnerParent*/true);
}
}
/// <summary>
/// A TextRange spanning the content of this element.
/// </summary>
internal TextRange TextRange
{
get
{
return new TextRange(this.ContentStart, this.ContentEnd);
}
}
/// <summary>
/// TextPointer preceding all content.
/// </summary>
/// <remarks>
/// The TextPointer returned always has its IsFrozen property set true
/// and LogicalDirection property set to LogicalDirection.Backward.
/// </remarks>
public TextPointer ContentStart
{
get
{
return _structuralCache.TextContainer.Start;
}
}
/// <summary>
/// TextPointer following all content.
/// </summary>
/// <remarks>
/// The TextPointer returned always has its IsFrozen property set true
/// and LogicalDirection property set to LogicalDirection.Forward.
/// </remarks>
public TextPointer ContentEnd
{
get
{
return _structuralCache.TextContainer.End;
}
}
#region Public Dynamic Properties
/// <summary>
/// DependencyProperty for <see cref="FontFamily" /> property.
/// </summary>
public static readonly DependencyProperty FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner(_typeofThis);
/// <summary>
/// The FontFamily property specifies the font family.
/// </summary>
[Localizability(
LocalizationCategory.Font,
Modifiability = Modifiability.Unmodifiable
)]
public FontFamily FontFamily
{
get { return (FontFamily) GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="FontStyle" /> property.
/// </summary>
public static readonly DependencyProperty FontStyleProperty =
TextElement.FontStyleProperty.AddOwner(_typeofThis);
/// <summary>
/// The FontStyle property requests normal, italic, and oblique faces within a font family.
/// </summary>
public FontStyle FontStyle
{
get { return (FontStyle) GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="FontWeight" /> property.
/// </summary>
public static readonly DependencyProperty FontWeightProperty =
TextElement.FontWeightProperty.AddOwner(_typeofThis);
/// <summary>
/// The FontWeight property specifies the weight of the font.
/// </summary>
public FontWeight FontWeight
{
get { return (FontWeight) GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="FontStretch" /> property.
/// </summary>
public static readonly DependencyProperty FontStretchProperty =
TextElement.FontStretchProperty.AddOwner(_typeofThis);
/// <summary>
/// The FontStretch property selects a normal, condensed, or extended face from a font family.
/// </summary>
public FontStretch FontStretch
{
get { return (FontStretch) GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="FontSize" /> property.
/// </summary>
public static readonly DependencyProperty FontSizeProperty =
TextElement.FontSizeProperty.AddOwner(
_typeofThis);
/// <summary>
/// The FontSize property specifies the size of the font.
/// </summary>
[TypeConverter(typeof(FontSizeConverter))]
[Localizability(LocalizationCategory.None)]
public double FontSize
{
get { return (double) GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="Foreground" /> property.
/// </summary>
public static readonly DependencyProperty ForegroundProperty =
TextElement.ForegroundProperty.AddOwner(
_typeofThis);
/// <summary>
/// The Foreground property specifies the foreground brush of an element's text content.
/// </summary>
public Brush Foreground
{
get { return (Brush) GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="Background" /> property.
/// </summary>
public static readonly DependencyProperty BackgroundProperty =
TextElement.BackgroundProperty.AddOwner(
_typeofThis,
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// The Background property defines the brush used to fill the content area.
/// </summary>
public Brush Background
{
get { return (Brush) GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="TextEffects" /> property.
/// </summary>
public static readonly DependencyProperty TextEffectsProperty =
TextElement.TextEffectsProperty.AddOwner(
_typeofThis,
new FrameworkPropertyMetadata(
new FreezableDefaultValueFactory(TextEffectCollection.Empty),
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// The TextEffects property specifies effects that are added to the text of an element.
/// </summary>
public TextEffectCollection TextEffects
{
get { return (TextEffectCollection) GetValue(TextEffectsProperty); }
set { SetValue(TextEffectsProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="TextAlignment" /> property.
/// </summary>
public static readonly DependencyProperty TextAlignmentProperty =
Block.TextAlignmentProperty.AddOwner(_typeofThis);
/// <summary>
/// The TextAlignment property specifies the alignmnet of the element.
/// </summary>
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="FlowDirection" /> property.
/// </summary>
public static readonly DependencyProperty FlowDirectionProperty =
Block.FlowDirectionProperty.AddOwner(_typeofThis);
/// <summary>
/// The FlowDirection property specifies the flow direction of the element.
/// </summary>
public FlowDirection FlowDirection
{
get { return (FlowDirection)GetValue(FlowDirectionProperty); }
set { SetValue(FlowDirectionProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="LineHeight" /> property.
/// </summary>
public static readonly DependencyProperty LineHeightProperty =
Block.LineHeightProperty.AddOwner(_typeofThis);
/// <summary>
/// The LineHeight property specifies the height of each generated line box.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double LineHeight
{
get { return (double)GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="LineStackingStrategy" /> property.
/// </summary>
public static readonly DependencyProperty LineStackingStrategyProperty =
Block.LineStackingStrategyProperty.AddOwner(_typeofThis);
/// <summary>
/// The LineStackingStrategy property specifies how lines are placed
/// </summary>
public LineStackingStrategy LineStackingStrategy
{
get { return (LineStackingStrategy)GetValue(LineStackingStrategyProperty); }
set { SetValue(LineStackingStrategyProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="ColumnWidth" /> property.
/// </summary>
public static readonly DependencyProperty ColumnWidthProperty =
DependencyProperty.Register(
"ColumnWidth",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// The minimum width of each column. If IsColumnWidthIsFlexible is True, then this
/// value is clamped to be no larger than the width of the page (specified by
/// PageWidth or PageSize) minus the PagePadding.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
public double ColumnWidth
{
get { return (double)GetValue(ColumnWidthProperty); }
set { SetValue(ColumnWidthProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="ColumnGap" /> property.
/// </summary>
public static readonly DependencyProperty ColumnGapProperty =
DependencyProperty.Register(
"ColumnGap",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidColumnGap));
/// <summary>
/// The distance between each column. This value is clamped to be no larger than
/// the width of the page (specified by PageWidth or PageSize) minus the PagePadding.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
public double ColumnGap
{
get { return (double) GetValue(ColumnGapProperty); }
set { SetValue(ColumnGapProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="IsColumnWidthFlexible" /> property.
/// </summary>
public static readonly DependencyProperty IsColumnWidthFlexibleProperty =
DependencyProperty.Register(
"IsColumnWidthFlexible",
typeof(bool),
_typeofThis,
new FrameworkPropertyMetadata(
true,
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Whether the width of columns is flexible. If this property is true, then columns
/// will frequently be wider than ColumnWidth. If false, columns will always be exactly
/// ColumnWidth (as long as the value is smaller than the width of the page minus padding).
/// </summary>
public bool IsColumnWidthFlexible
{
get { return (bool)GetValue(IsColumnWidthFlexibleProperty); }
set { SetValue(IsColumnWidthFlexibleProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="ColumnRuleWidth" /> property.
/// </summary>
public static readonly DependencyProperty ColumnRuleWidthProperty =
DependencyProperty.Register(
"ColumnRuleWidth",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidColumnRuleWidth));
/// <summary>
/// The width of the line drawn in between columns. This value is clamped
/// to be no larger than the ColumnGap.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
public double ColumnRuleWidth
{
get { return (double)GetValue(ColumnRuleWidthProperty); }
set { SetValue(ColumnRuleWidthProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="ColumnRuleBrush" /> property.
/// </summary>
public static readonly DependencyProperty ColumnRuleBrushProperty =
DependencyProperty.Register(
"ColumnRuleBrush",
typeof(Brush),
_typeofThis,
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// The brush used to draw the line between columns.
/// </summary>
public Brush ColumnRuleBrush
{
get { return (Brush)GetValue(ColumnRuleBrushProperty); }
set { SetValue(ColumnRuleBrushProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="IsOptimalParagraphEnabled" /> property.
/// </summary>
public static readonly DependencyProperty IsOptimalParagraphEnabledProperty =
DependencyProperty.Register(
"IsOptimalParagraphEnabled",
typeof(bool),
_typeofThis,
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Whether FlowDocument should attempt to construct optimal text paragraphs.
/// </summary>
public bool IsOptimalParagraphEnabled
{
get { return (bool)GetValue(IsOptimalParagraphEnabledProperty); }
set { SetValue(IsOptimalParagraphEnabledProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="PageWidth" /> property.
/// </summary>
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register(
"PageWidth",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnPageMetricsChanged),
new CoerceValueCallback(CoercePageWidth)),
new ValidateValueCallback(IsValidPageSize));
/// <summary>
/// The width of pages returned by GetPage. This value takes precedence over
/// PageSize.Width, MinPageWidth, and MaxPageWidth.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double PageWidth
{
get { return (double) GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="MinPageWidth" /> property.
/// </summary>
public static readonly DependencyProperty MinPageWidthProperty =
DependencyProperty.Register(
"MinPageWidth",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMinPageWidthChanged)),
new ValidateValueCallback(IsValidMinPageSize));
/// <summary>
/// The minimum width of pages returned by GetPage. This value takes
/// precedence over PageSize.Width, but not PageWidth.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double MinPageWidth
{
get { return (double) GetValue(MinPageWidthProperty); }
set { SetValue(MinPageWidthProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="MaxPageWidth" /> property.
/// </summary>
public static readonly DependencyProperty MaxPageWidthProperty =
DependencyProperty.Register(
"MaxPageWidth",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.PositiveInfinity,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMaxPageWidthChanged),
new CoerceValueCallback(CoerceMaxPageWidth)),
new ValidateValueCallback(IsValidMaxPageSize));
/// <summary>
/// The maximum width of pages returned by GetPage. This value takes
/// precedence over PageSize.Width, but not PageWidth.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double MaxPageWidth
{
get { return (double) GetValue(MaxPageWidthProperty); }
set { SetValue(MaxPageWidthProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="PageHeight" /> property.
/// </summary>
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register(
"PageHeight",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnPageMetricsChanged),
new CoerceValueCallback(CoercePageHeight)),
new ValidateValueCallback(IsValidPageSize));
/// <summary>
/// The height of pages returned by GetPage. This value takes precedence
/// over PageSize.Height, MinPageHeight, and MaxPageHeight.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double PageHeight
{
get { return (double) GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="MinPageHeight" /> property.
/// </summary>
public static readonly DependencyProperty MinPageHeightProperty =
DependencyProperty.Register(
"MinPageHeight",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMinPageHeightChanged)),
new ValidateValueCallback(IsValidMinPageSize));
/// <summary>
/// The minimum height of pages returned by GetPage. This value takes
/// precedence over PageSize.Height, but not PageHeight.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double MinPageHeight
{
get { return (double) GetValue(MinPageHeightProperty); }
set { SetValue(MinPageHeightProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="MaxPageHeight" /> property.
/// </summary>
public static readonly DependencyProperty MaxPageHeightProperty =
DependencyProperty.Register(
"MaxPageHeight",
typeof(double),
_typeofThis,
new FrameworkPropertyMetadata(
double.PositiveInfinity,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMaxPageHeightChanged),
new CoerceValueCallback(CoerceMaxPageHeight)),
new ValidateValueCallback(IsValidMaxPageSize));
/// <summary>
/// The maximum height of pages returned by GetPage. This value takes
/// precedence over PageSize.Height, but not PageHeight.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double MaxPageHeight
{
get { return (double) GetValue(MaxPageHeightProperty); }
set { SetValue(MaxPageHeightProperty, value); }
}
/// <summary>
/// DependencyProperty for <see cref="PagePadding" /> property.
/// </summary>
public static readonly DependencyProperty PagePaddingProperty =
DependencyProperty.Register(
"PagePadding",
typeof(Thickness),
_typeofThis,
new FrameworkPropertyMetadata(
new Thickness(Double.NaN),
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnPageMetricsChanged)),
new ValidateValueCallback(IsValidPagePadding));
/// <summary>
/// Padding applied between the page boundaries and the content of the page.
/// If the sum of the specified padding in a dimension is greater than the
/// corresponding page dimension, then the padding values in that dimension
/// will be proportionally reduced such that the sum of the padding in that
/// dimension is equal to the page dimension.
/// For example, if the PageSize is (100, 100) and PagePadding is (0, 120, 0, 80),
/// then the page will be formatted as if the PagePadding were actually (0, 60, 0, 40).
/// </summary>
public Thickness PagePadding
{
get { return (Thickness) GetValue(PagePaddingProperty); }
set { SetValue(PagePaddingProperty, value); }
}
/// <summary>
/// Class providing access to all text typography properties
/// </summary>
public Typography Typography
{
get
{
return new Typography(this);
}
}
/// <summary>
/// DependencyProperty for hyphenation property.
/// </summary>
public static readonly DependencyProperty IsHyphenationEnabledProperty =
Block.IsHyphenationEnabledProperty.AddOwner(_typeofThis);
/// <summary>
/// CLR property for hyphenation
/// </summary>
public bool IsHyphenationEnabled
{
get { return (bool)GetValue(IsHyphenationEnabledProperty); }
set { SetValue(IsHyphenationEnabledProperty, value); }
}
#endregion Public Dynamic Properties
#endregion Public Properties
/// <summary>
/// Sets the DPI for the FlowDocument, causing it to be remeasured and re-rendered. Should be used in Per Monitor DPI scenarios.
/// </summary>
public void SetDpi(DpiScale dpiInfo)
{
if (dpiInfo.PixelsPerDip != _pixelsPerDip)
{
_pixelsPerDip = dpiInfo.PixelsPerDip;
if (StructuralCache.HasPtsContext())
{
StructuralCache.TextFormatterHost.PixelsPerDip = _pixelsPerDip;
}
// Notify formatter about content invalidation.
_formatter?.OnContentInvalidated(true);
}
}
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// Notification that a specified property has been invalidated
/// </summary>
/// <param name="e">EventArgs that contains the property, metadata, old value, and new value for this change</param>
protected sealed override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// Always call base.OnPropertyChanged, otherwise Property Engine will not work.
base.OnPropertyChanged(e);
if (e.IsAValueChange || e.IsASubPropertyChange)
{
// Skip caches invalidation if content has not been formatted yet - non of caches are valid,
// so they will be aquired during first formatting (full format).
if (_structuralCache != null && _structuralCache.IsFormattedOnce)
{
FrameworkPropertyMetadata fmetadata = e.Metadata as FrameworkPropertyMetadata;
if (fmetadata != null)
{
bool affectsRender = (fmetadata.AffectsRender &&
(e.IsAValueChange || !fmetadata.SubPropertiesDoNotAffectRender));
if (fmetadata.AffectsMeasure || fmetadata.AffectsArrange || affectsRender || fmetadata.AffectsParentMeasure || fmetadata.AffectsParentArrange)
{
// Detect invalid content change operations.
if (_structuralCache.IsFormattingInProgress)
{
_structuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.FlowDocumentInvalidContnetChange);
}
// None of FlowDocument properties can invalidate structural caches (the NameTable),
// but most likely it invalidates format caches. Invalidate all format caches
// accumulated in the NameTable.
_structuralCache.InvalidateFormatCache(!affectsRender);
// Notify formatter about content invalidation.
if (_formatter != null)
{
_formatter.OnContentInvalidated(!affectsRender);
}
}
}
}
}
}
/// <summary>
/// Creates AutomationPeer (<see cref="ContentElement.OnCreateAutomationPeer"/>)
/// </summary>
protected override AutomationPeer OnCreateAutomationPeer()
{
return new DocumentAutomationPeer(this);
}
#endregion Protected Methods
//-------------------------------------------------------------------
//
// Protected Properties
//
//-------------------------------------------------------------------
#region Protected Properties
/// <summary>
/// Returns enumerator to logical children.
/// </summary>
protected internal override IEnumerator LogicalChildren
{
get
{
return new RangeContentEnumerator(_structuralCache.TextContainer.Start, _structuralCache.TextContainer.End);
}
}
/// <summary>
/// Fetches the value of the IsEnabled property
/// </summary>
/// <remarks>
/// We want to coerce ContentElements and UIElement children of FlowDocument to be disabled in _editable_ content (ie, RichTextBox).
/// In read-only mode, in say the FlowDocumentReader, we don't want any coercing.
/// </remarks>
protected override bool IsEnabledCore
{
get
{
if (!base.IsEnabledCore)
{
return false;
}
RichTextBox parentRichTextBox = this.Parent as RichTextBox;
return (parentRichTextBox == null) ? true : parentRichTextBox.IsDocumentEnabled;
}
}
#endregion Protected Properties
//-------------------------------------------------------------------
//
// Internal Methods
//
//-------------------------------------------------------------------
#region Internal Methods
/// <summary>
/// Returns a ContentPosition for an object.
/// </summary>
/// <param name="element">Object within this element's tree.</param>
/// <returns>Returns a ContentPosition for an object.</returns>
internal ContentPosition GetObjectPosition(Object element)
{
TextPointer flowContentPosition;
ITextPointer textPointer = null;
DependencyObject parentOfEmbeddedElement;
// If element is 'this', return ContentPosition representing ContentStart.
if (element == this)
{
textPointer = this.ContentStart;
}
// If element is a TextElement, return its ContentStart.
else if (element is TextElement)
{
textPointer = ((TextElement)element).ContentStart;
}
// Otherwise we are dealing with embedded element. Find its position in the
// TextContainer and return it.
// It is possible that somebody asks for FrameworkElement that is not an immediate
// child of FlowDocument's content. In such case walk up the parent chain and
// get FrameworkElement that is directly embedded within FlowDocument's content.
else if (element is FrameworkElement)
{
parentOfEmbeddedElement = null;
while (element is FrameworkElement)
{
parentOfEmbeddedElement = LogicalTreeHelper.GetParent((DependencyObject)element);
if (parentOfEmbeddedElement == null)
{
parentOfEmbeddedElement = VisualTreeHelper.GetParent((Visual)element);
}
if (!(parentOfEmbeddedElement is FrameworkElement))
{
break;
}
element = parentOfEmbeddedElement;
}
if (parentOfEmbeddedElement is BlockUIContainer || parentOfEmbeddedElement is InlineUIContainer)
{
textPointer = TextContainerHelper.GetTextPointerForEmbeddedObject((FrameworkElement)element);
}
}
// Check if the TextPointer belongs to our tree.
if (textPointer != null && textPointer.TextContainer != _structuralCache.TextContainer)
{
textPointer = null;
}
flowContentPosition = textPointer as TextPointer;
return (flowContentPosition != null) ? flowContentPosition : ContentPosition.Missing;
}
/// <summary>
/// OnChildDesiredSizeChanged
/// Called from FlowDocumentPage for IContentHost implementation
/// This should be private but cannot be access from its
/// FlowDocumentPage because of protection level. Find a workaround
/// </summary>
/// <param name="child"></param>
internal void OnChildDesiredSizeChanged(UIElement child)
{
if (_structuralCache != null && _structuralCache.IsFormattedOnce && !_structuralCache.ForceReformat)
{
// If executed during formatting process, delay invalidation.
// This may happen during formatting when text host notifies its about
// baseline changes.
if (_structuralCache.IsFormattingInProgress)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(OnChildDesiredSizeChangedAsync), child);
return;
}
// Get start and end positions
int childStartIndex = TextContainerHelper.GetCPFromEmbeddedObject(child, ElementEdge.BeforeStart);
if (childStartIndex < 0)
{
return;
}
TextPointer childStart = new TextPointer(_structuralCache.TextContainer.Start);
childStart.MoveByOffset(childStartIndex);
TextPointer childEnd = new TextPointer(childStart);
childEnd.MoveByOffset(TextContainerHelper.EmbeddedObjectLength);
// Create new DTR for changing UIElement and add it to DRTList.
DirtyTextRange dtr = new DirtyTextRange(childStartIndex, TextContainerHelper.EmbeddedObjectLength, TextContainerHelper.EmbeddedObjectLength);
_structuralCache.AddDirtyTextRange(dtr);
// Notify formatter about content invalidation.
if (_formatter != null)
{
_formatter.OnContentInvalidated(true, childStart, childEnd);
}
}
}
/// <summary>
/// Do delayed initialization before first formatting.
/// </summary>
internal void InitializeForFirstFormatting()
{
_structuralCache.TextContainer.Changing += new EventHandler(OnTextContainerChanging);
_structuralCache.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange);
_structuralCache.TextContainer.Highlights.Changed += new HighlightChangedEventHandler(OnHighlightChanged);
}
/// <summary>
/// Clear the TextContainer and unregister events. Called by TextBox on style change.
/// </summary>
internal void Uninitialize()
{
_structuralCache.TextContainer.Changing -= new EventHandler(OnTextContainerChanging);
_structuralCache.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
_structuralCache.TextContainer.Highlights.Changed -= new HighlightChangedEventHandler(OnHighlightChanged);
_structuralCache.IsFormattedOnce = false;
}
/// <summary>
/// Compute margin for a page.
/// </summary>
internal Thickness ComputePageMargin()
{
double lineHeight = DynamicPropertyReader.GetLineHeightValue(this);
Thickness pageMargin = this.PagePadding;
// If Padding value is 'Auto', treat it as 1*LineHeight.
if (double.IsNaN(pageMargin.Left))
{
pageMargin.Left = lineHeight;
}
if (double.IsNaN(pageMargin.Top))
{
pageMargin.Top = lineHeight;
}
if (double.IsNaN(pageMargin.Right))
{
pageMargin.Right = lineHeight;
}
if (double.IsNaN(pageMargin.Bottom))
{
pageMargin.Bottom = lineHeight;
}
return pageMargin;
}
/// <summary>
/// Called before the parent is changed to the new value.
/// We listen to parent change notifications to enforce coercing FlowDocument's IsEnabled property to false
/// (when it is parented by RichTextBox).
/// This property coersion is done in 2 parts.
/// 1. Implement IsEnabledCore for property coersion
/// 2. Listen to changes to parent
/// (2) needs to be done, since property system does not coerce default values.
/// Listening to parent changes guarantees that every time FlowDocument is removed or connected to a RichTextBox parent,
/// we explicitly coerce IsEnabled for the new tree.
/// </summary>
/// <param name="newParent"></param>
internal override void OnNewParent(DependencyObject newParent)
{
DependencyObject oldParent = this.Parent;
base.OnNewParent(newParent);
if (newParent is RichTextBox || oldParent is RichTextBox)
{
CoerceValue(IsEnabledProperty);
}
}
#endregion Internal Methods
//-------------------------------------------------------------------
//
// Internal Properties
//
//-------------------------------------------------------------------
#region Internal Properties
/// <summary>
/// An object which formats botomless content.
/// </summary>
internal FlowDocumentFormatter BottomlessFormatter
{
get
{
if (_formatter != null && !(_formatter is FlowDocumentFormatter))
{
_formatter.Suspend();
_formatter = null;
}
if (_formatter == null)
{
_formatter = new FlowDocumentFormatter(this);
}
return (FlowDocumentFormatter)_formatter;
}
}
/// <summary>
/// StructuralCache.
/// </summary>
internal StructuralCache StructuralCache
{
get
{
return _structuralCache;
}
}
/// <summary>
/// Typography properties group.
/// </summary>
internal TypographyProperties TypographyPropertiesGroup
{
get
{
if (_typographyPropertiesGroup == null)
{
_typographyPropertiesGroup = TextElement.GetTypographyProperties(this);
}
return _typographyPropertiesGroup;
}
}
/// <summary>
/// TextWrapping property value (set by TextBox/RichTextBox)
/// </summary>
internal TextWrapping TextWrapping
{
get
{
return _textWrapping;
}
set
{
_textWrapping = value;
}
}
/// <summary>
/// Formatter value
/// </summary>
internal IFlowDocumentFormatter Formatter
{
get
{
return _formatter;
}
}
//-------------------------------------------------------------------
// Is layout data is in a valid state.
//-------------------------------------------------------------------
internal bool IsLayoutDataValid
{
get
{
if(_formatter != null)
{
return _formatter.IsLayoutDataValid;
}
return false;
}
}
//-------------------------------------------------------------------
// TextContainer associated with this FlowDocument.
//-------------------------------------------------------------------
internal TextContainer TextContainer
{
get
{
return _structuralCache.TextContainer;
}
}
//-------------------------------------------------------------------
// DPI at which the FlowDocument needs to be rendered.
//-------------------------------------------------------------------
internal double PixelsPerDip
{
get { return _pixelsPerDip; }
set { _pixelsPerDip = value; }
}
#endregion Internal Properties
//-------------------------------------------------------------------
//
// Internal Events
//
//-------------------------------------------------------------------
#region Internal Events
/// <summary>
/// Fired when a PageSize property is changed
/// </summary>
internal event EventHandler PageSizeChanged;
#endregion Internal Events
//-------------------------------------------------------------------
//
// Private Methods
//
//-------------------------------------------------------------------
#region Private Methods
/// <summary>
/// One of the properties which comprises TypographyProperties has changed -- reset cache.
/// </summary>
private static void OnTypographyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FlowDocument)d)._typographyPropertiesGroup = null;
}
/// <summary>
/// OnChildDesiredSizeChanged delayed to avoid changes during page
/// formatting.
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object OnChildDesiredSizeChangedAsync(object arg)
{
OnChildDesiredSizeChanged(arg as UIElement);
return null;
}
/// <summary>
/// Initialize FlowDocument.
/// </summary>
/// <param name="textContainer"></param>
private void Initialize(TextContainer textContainer)
{
if (textContainer == null)
{
// Create text tree that contains content of the element.
textContainer = new TextContainer(this, false /* plainTextOnly */);
}
// Create structural cache object
_structuralCache = new StructuralCache(this, textContainer);
// Get rid of the current formatter.
if (_formatter != null)
{
_formatter.Suspend();
_formatter = null;
}
}
/// <summary>
/// Respond to page metrics changes.
/// </summary>
private static void OnPageMetricsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FlowDocument fd = (FlowDocument)d;
if (fd._structuralCache != null && fd._structuralCache.IsFormattedOnce)
{
// Notify formatter about content invalidation.
if (fd._formatter != null)
{
// Any change of page metrics invalidates the layout.
// Hence page metrics change is treated in the same way as ContentChanged
// spanning entire content.
fd._formatter.OnContentInvalidated(true);
}
// Fire notification about the PageSize change - needed in RichTextBox
if (fd.PageSizeChanged != null)
{
// NOTE: May execute external code, so it is possible to get
// an exception here.
fd.PageSizeChanged(fd, EventArgs.Empty);
}
}
}
/// <summary>
/// Respond to MinPageWidth change.
/// </summary>
private static void OnMinPageWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MaxPageWidthProperty);
d.CoerceValue(PageWidthProperty);
}
/// <summary>
/// Respond to MinPageHeight change.
/// </summary>
private static void OnMinPageHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MaxPageHeightProperty);
d.CoerceValue(PageHeightProperty);
}
/// <summary>
/// Respond to MaxPageWidth change.
/// </summary>
private static void OnMaxPageWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(PageWidthProperty);
}
/// <summary>
/// Respond to MaxPageHeight change.
/// </summary>
private static void OnMaxPageHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(PageHeightProperty);
}
/// <summary>
/// Coerce MaxPageWidth value.
/// </summary>
private static object CoerceMaxPageWidth(DependencyObject d, object value)
{
FlowDocument fd = (FlowDocument) d;
double max = (double) value;
double min = fd.MinPageWidth;
if (max < min)
{
return min;
}
return value;
}
/// <summary>
/// Coerce MaxPageHeight value.
/// </summary>
private static object CoerceMaxPageHeight(DependencyObject d, object value)
{
FlowDocument fd = (FlowDocument) d;
double max = (double) value;
double min = fd.MinPageHeight;
if (max < min)
{
return min;
}
return value;
}
/// <summary>
/// Coerce PageWidth value.
/// </summary>
private static object CoercePageWidth(DependencyObject d, object value)
{
FlowDocument fd = (FlowDocument) d;
double width = (double) value;
if (!double.IsNaN(width))
{
double max = fd.MaxPageWidth;
if (width > max)
{
width = max;
}
double min = fd.MinPageWidth;
if (width < min)
{
width = min;
}
}
return value;
}
/// <summary>
/// Coerce PageHeight value.
/// </summary>
private static object CoercePageHeight(DependencyObject d, object value)
{
FlowDocument fd = (FlowDocument) d;
double height = (double) value;
if (!double.IsNaN(height))
{
double max = fd.MaxPageHeight;
if (height > max)
{
height = max;
}
double min = fd.MinPageHeight;
if (height < min)
{
height = min;
}
}
return value;
}
/// <summary>
/// Invalidates a portion of text affected by a highlight change.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnHighlightChanged(object sender, HighlightChangedEventArgs args)
{
TextSegment textSegment;
int i;
Invariant.Assert(args != null);
Invariant.Assert(args.Ranges != null);
Invariant.Assert(_structuralCache != null && _structuralCache.IsFormattedOnce, "Unexpected Highlights.Changed callback before first format!");
// Detect invalid content change operations.
if (_structuralCache.IsFormattingInProgress)
{
_structuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.FlowDocumentInvalidContnetChange);
}
// The only supported highlight type for FlowDocument is SpellerHightlight.
// TextSelection and HighlightComponent are ignored, because they are handled by
// separate layer.
if (args.OwnerType != typeof(SpellerHighlightLayer))
{
return;
}
if (args.Ranges.Count > 0)
{
// Invalidate affected pages and break records.
// We DTR invalidate if we're using a formatter as well for incremental update.
if (_formatter == null || !(_formatter is FlowDocumentFormatter))
{
_structuralCache.InvalidateFormatCache(/*Clear structure*/ false);
}
// Notify formatter about content invalidation.
if (_formatter != null)
{
for (i = 0; i < args.Ranges.Count; i++)
{
textSegment = (TextSegment)args.Ranges[i];
_formatter.OnContentInvalidated(false, textSegment.Start, textSegment.End);
if (_formatter is FlowDocumentFormatter)
{
DirtyTextRange dtr = new DirtyTextRange(textSegment.Start.Offset,
textSegment.Start.GetOffsetToPosition(textSegment.End),
textSegment.Start.GetOffsetToPosition(textSegment.End)
);
_structuralCache.AddDirtyTextRange(dtr);
}
}
}
}
}
/// <summary>
/// Handler for TextContainer changing notifications.
/// </summary>
private void OnTextContainerChanging(object sender, EventArgs args)
{
Invariant.Assert(sender == _structuralCache.TextContainer, "Received text change for foreign TextContainer.");
Invariant.Assert(_structuralCache != null && _structuralCache.IsFormattedOnce, "Unexpected TextContainer.Changing callback before first format!");
// Detect invalid content change operations.
if (_structuralCache.IsFormattingInProgress)
{
_structuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.FlowDocumentInvalidContnetChange);
}
// Remember the fact that content is changing.
// OnTextContainerChange has to be received after this event.
_structuralCache.IsContentChangeInProgress = true;
}
/// <summary>
/// Handler for TextContainer change notifications.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args)
{
DirtyTextRange dtr;
ITextPointer segmentEnd;
Invariant.Assert(args != null);
Invariant.Assert(sender == _structuralCache.TextContainer);
Invariant.Assert(_structuralCache != null && _structuralCache.IsFormattedOnce, "Unexpected TextContainer.Change callback before first format!");
if (args.Count == 0)
{
// A no-op for this control. Happens when IMECharCount updates happen
// without corresponding SymbolCount changes.
return;
}
try
{
// Detect invalid content change operations.
if (_structuralCache.IsFormattingInProgress)
{
_structuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.FlowDocumentInvalidContnetChange);
}
// Since content is changeing, do partial invalidation of BreakRecordTable.
if (args.TextChange != TextChangeType.ContentRemoved)
{
segmentEnd = args.ITextPosition.CreatePointer(args.Count, LogicalDirection.Forward);
}
else
{
segmentEnd = args.ITextPosition;
}
// Invalidate affected pages and break records.
// We DTR invalidate if we're using a formatter as well for incremental update.
if (!args.AffectsRenderOnly || (_formatter != null && _formatter is FlowDocumentFormatter))
{
// Create new DTR for changing range and add it to DRTList.
dtr = new DirtyTextRange(args);
_structuralCache.AddDirtyTextRange(dtr);
}
else
{
// Clear format caches.
_structuralCache.InvalidateFormatCache(/*Clear structure*/ false);
}
// Notify formatter about content invalidation.
if (_formatter != null)
{
_formatter.OnContentInvalidated(!args.AffectsRenderOnly, args.ITextPosition, segmentEnd);
}
}
finally
{
// Content has been changed, so reset appropriate flag.
_structuralCache.IsContentChangeInProgress = false;
}
}
private static bool IsValidPageSize(object o)
{
double value = (double)o;
double maxSize = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(value))
{
return true;
}
if (value < 0 || value > maxSize)
{
return false;
}
return true;
}
private static bool IsValidMinPageSize(object o)
{
double value = (double)o;
double maxSize = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(value))
{
return false;
}
if (!Double.IsNegativeInfinity(value) && (value < 0 || value > maxSize))
{
return false;
}
return true;
}
private static bool IsValidMaxPageSize(object o)
{
double value = (double)o;
double maxSize = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(value))
{
return false;
}
if (!Double.IsPositiveInfinity(value) && (value < 0 || value > maxSize))
{
return false;
}
return true;
}
private static bool IsValidPagePadding(object o)
{
Thickness value = (Thickness)o;
return Block.IsValidThickness(value, /*allow NaN*/true);
}
private static bool IsValidColumnRuleWidth(object o)
{
double ruleWidth = (double)o;
double maxRuleWidth = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(ruleWidth) || ruleWidth < 0 || ruleWidth > maxRuleWidth)
{
return false;
}
return true;
}
private static bool IsValidColumnGap(object o)
{
double gap = (double)o;
double maxGap = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(gap))
{
// Default value.
return true;
}
if (gap < 0 || gap > maxGap)
{
return false;
}
return true;
}
#endregion Private methods
//-------------------------------------------------------------------
//
// Private Fields
//
//-------------------------------------------------------------------
#region Private Fields
private StructuralCache _structuralCache; // Structural cache for the content.
private TypographyProperties _typographyPropertiesGroup; // Cache for typography properties.
private IFlowDocumentFormatter _formatter; // Current formatter asociated with FlowDocument.
private TextWrapping _textWrapping = TextWrapping.Wrap; // internal cache for TextBox/RichTextBox
private double _pixelsPerDip = MS.Internal.FontCache.Util.PixelsPerDip;
#endregion Private Fields
//-------------------------------------------------------------------
//
// IAddChild Members
//
//-------------------------------------------------------------------
#region IAddChild Members
///<summary>
/// Called to Add the object as a Child.
///</summary>
///<param name="value">
/// Object to add as a child
///</param>
void IAddChild.AddChild(Object value)
{
ArgumentNullException.ThrowIfNull(value);
if (!TextSchema.IsValidChildOfContainer(/*parentType:*/_typeofThis, /*childType:*/value.GetType()))
{
throw new ArgumentException(SR.Format(SR.TextSchema_ChildTypeIsInvalid, _typeofThis.Name, value.GetType().Name));
}
// Checking that the element inserted does not have a parent
if (value is TextElement && ((TextElement)value).Parent != null)
{
throw new ArgumentException(SR.Format(SR.TextSchema_TheChildElementBelongsToAnotherTreeAlready, value.GetType().Name));
}
if (value is Block)
{
TextContainer textContainer = _structuralCache.TextContainer;
((Block)value).RepositionWithContent(textContainer.End);
}
else
{
Invariant.Assert(false); // We do not expect anything except Blocks on top level of a FlowDocument
}
}
///<summary>
/// Called when text appears under the tag in markup
///</summary>
///<param name="text">
/// Text to Add to the Object
///</param>
void IAddChild.AddText(string text)
{
XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this);
}
#endregion IAddChild Members
//-------------------------------------------------------------------
//
// IServiceProvider Members
//
//-------------------------------------------------------------------
#region IServiceProvider Members
/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <remarks>
/// FlowDocument supports only TextContainer.
/// </remarks>
/// <param name="serviceType">
/// An object that specifies the type of service object to get.
/// </param>
/// <returns>
/// A service object of type serviceType. A null reference if there is no
/// service object of type serviceType.
/// </returns>
object IServiceProvider.GetService(Type serviceType)
{
ArgumentNullException.ThrowIfNull(serviceType);
if (serviceType == typeof(ITextContainer))
{
return _structuralCache.TextContainer;
}
else if (serviceType == typeof(TextContainer))
{
return _structuralCache.TextContainer as TextContainer;
}
return null;
}
#endregion IServiceProvider Members
//-------------------------------------------------------------------
//
// IDocumentPaginatorSource Members
//
//-------------------------------------------------------------------
#region IDocumentPaginatorSource Members
/// <summary>
/// An object which paginates content.
/// </summary>
DocumentPaginator IDocumentPaginatorSource.DocumentPaginator
{
get
{
if (_formatter != null && !(_formatter is FlowDocumentPaginator))
{
_formatter.Suspend();
_formatter = null;
}
if (_formatter == null)
{
_formatter = new FlowDocumentPaginator(this);
}
return (FlowDocumentPaginator)_formatter;
}
}
#endregion IDocumentPaginatorSource Members
}
}
|