Beispiel #1
0
        /// <summary>
        /// Removes redaction marks from the specified range.
        /// </summary>
        /// <param name="Range">The Range from which to clear redaction marks.</param>
        /// <param name="UnmarkAll">True to also clear any subranges (e.g. text boxes), False otherwise.</param>
        private void UnmarkRange(Word.Range Range, bool UnmarkAll)
        {
            //text boxes in headers/footers aren't in the text box story
            if (UnmarkAll && (int)Range.StoryType > 5 && Range.ShapeRange.Count > 0)
            {
                foreach (Word.Shape Shape in Range.ShapeRange)
                {
                    Word.Range ShapeRange = RedactCommon.RangeFromShape(Shape);
                    if (ShapeRange != null)
                    {
                        UnmarkRange(ShapeRange, true);
                    }
                }
            }

            //unmark the range
            List <RangeDataEx> RangeMarkers = RedactCommon.GetAllMarkedRanges(Range, ShadingColor, true);

            foreach (RangeDataEx UniqueRange in RangeMarkers)
            {
                Range.Start = UniqueRange.Start;
                Range.End   = UniqueRange.End;

                Range.Font.Shading.BackgroundPatternColor = Word.WdColor.wdColorAutomatic;
            }

            //also catch other instances of the same StoryRange
            if (UnmarkAll && Range.NextStoryRange != null)
            {
                UnmarkRange(Range.NextStoryRange, true);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Finds the previous redaction mark in previous document stories of the current type (e.g. previous headers).
        /// </summary>
        /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the output mark if one is found.</param>
        /// <returns>True if a mark was found, otherwise False.</returns>
        private static bool FindPreviousMarkInStoryRanges(ref Word.Range CurrentRange)
        {
            object CollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;

            Word.WdHeaderFooterIndex?headerFooterId = null;

            Word.Range PreviousStoryRange = RedactCommon.PreviousStoryRange(CurrentRange);
            while (PreviousStoryRange != null)
            {
                //if we're in headers/footers, we need to get our location so we can update the current view
                if (CurrentRange.StoryType != Word.WdStoryType.wdTextFrameStory)
                {
                    headerFooterId = RedactCommon.GetHeaderFooterType(CurrentRange.StoryType);
                }

                CurrentRange       = PreviousStoryRange;
                PreviousStoryRange = RedactCommon.PreviousStoryRange(CurrentRange);
                CurrentRange.Collapse(ref CollapseEnd);
                if (FindPreviousMarkInCurrentStoryRange(ref CurrentRange))
                {
                    //move the focus in the view
                    if (RedactCommon.IsHeaderFooter(CurrentRange.StoryType))
                    {
                        MoveHeaderFooterFocus(CurrentRange, RedactCommon.FindSectionWithHeaderFooter(CurrentRange.Document, CurrentRange), headerFooterId);
                    }
                    return(true);
                }
            }
            return(false);
        }
Beispiel #3
0
 /// <summary>
 /// Extends the current range's starting point to the beginning of the mark.
 /// </summary>
 /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the full mark if one is found.</param>
 /// <param name="Collapse">True to collapse the result to the start of the range, otherwise False.</param>
 private static void ExtendPreviousMark(ref Word.Range SelectionRange, bool Collapse)
 {
     if (SelectionRange.Start != 0)
     {
         //move before the current mark (if we are in one)
         if (RedactCommon.IsMarkedRange(SelectionRange, ShadingColor))
         {
             Word.Range SearchRange = SelectionRange.Duplicate;
             while (FindPreviousMarkInCurrentStoryRange(ref SearchRange))
             {
                 if (SearchRange.End == SelectionRange.Start && (bool)SearchRange.get_Information(Word.WdInformation.wdWithInTable) == (bool)SelectionRange.get_Information(Word.WdInformation.wdWithInTable))
                 {
                     SelectionRange.Start = SearchRange.Start;
                     if (Collapse)
                     {
                         SelectionRange.End = SearchRange.Start;
                     }
                 }
                 else
                 {
                     break;
                 }
             }
         }
     }
 }
Beispiel #4
0
        /// <summary>
        /// Moves the focus to the appropriate header/footer.
        /// </summary>
        /// <param name="SelectionRange">The range containing the target header/footer.</param>
        /// <param name="sectionIdFinal">The section to be selected.</param>
        /// <param name="headerFooterId">The header/footer type to be selected.</param>
        internal static void MoveHeaderFooterFocus(Word.Range SelectionRange, int?sectionIdFinal, Word.WdHeaderFooterIndex?headerFooterId)
        {
            if (sectionIdFinal != null && headerFooterId != null)
            {
                Word.Window CurrentWindow = SelectionRange.Application.ActiveWindow;
                try
                {
                    Word.Section CurrentSection = SelectionRange.Document.Sections[(int)sectionIdFinal];
                    if (!RedactCommon.GetHeaderFooterVisibility(CurrentSection, (Word.WdHeaderFooterIndex)headerFooterId) || CurrentWindow.View.Type != Word.WdViewType.wdPrintView)
                    {
                        //if that header/footer isn't being shown, we need to fall back into normal view
                        CurrentWindow.View.Type = Word.WdViewType.wdNormalView;
                        CurrentSection.Range.Select();
                        CurrentWindow.View.SplitSpecial = RedactCommon.GetSplitTypeForStory(SelectionRange.StoryType);
                    }
                    else
                    {
                        //select the appropriate section
                        SelectionRange.Document.Sections[(int)sectionIdFinal].Range.Select();

                        //jump to the header/footer
                        CurrentWindow.View.SeekView = RedactCommon.GetSeekViewForStory(SelectionRange.StoryType);

                        Thread tTabSwitch = new Thread(StayOnReviewTab);
                        tTabSwitch.Start(Redaction.Properties.Resources.ReviewTab);
                    }
                }
                catch (COMException)
                {
                    System.Diagnostics.Debug.Fail("incorrect header/footer view movement detected");
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// Extends the current range's ending point to the end of the mark.
        /// </summary>
        /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the full mark if one is found.</param>
        /// <param name="Collapse">True to collapse the result to the end of the range, otherwise False.</param>
        private static void ExtendNextMark(ref Word.Range SelectionRange, bool Collapse)
        {
            if (SelectionRange.End < SelectionRange.StoryLength - 1)
            {
                //move before the current mark (if we are in one)
                if (RedactCommon.IsMarkedRange(SelectionRange, ShadingColor))
                {
                    Word.Range SearchRange = SelectionRange.Duplicate;
                    while (FindNextMarkInCurrentStoryRange(ref SearchRange))
                    {
                        if (SearchRange.Start == SelectionRange.End && (bool)SearchRange.get_Information(Word.WdInformation.wdWithInTable) == (bool)SelectionRange.get_Information(Word.WdInformation.wdWithInTable))
                        {
                            SelectionRange.End = SearchRange.End;
                            if (Collapse)
                            {
                                SelectionRange.Start = SearchRange.End;
                            }

                            //if we didn't progress in this iteration, break
                            if (SelectionRange.Start == SearchRange.Start && SelectionRange.End == SearchRange.End)
                            {
                                break;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Finds the next redaction mark in subsequent document stories.
        /// </summary>
        /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the output mark if one is found.</param>
        /// <returns>True if a mark was found, otherwise False.</returns>
        private bool FindNextMarkInOtherStory(ref Word.Range CurrentRange)
        {
            //move forward a story
            int NextStory = (int)CurrentRange.StoryType + 1;

            while (NextStory <= 17)
            {
                try
                {
                    CurrentRange = CurrentRange.Document.StoryRanges[(Word.WdStoryType)NextStory];
                    CurrentRange.Collapse(ref CollapseStart);

                    if (FindNextMarkInCurrentStory(ref CurrentRange))
                    {
                        if (RedactCommon.IsHeaderFooter((Word.WdStoryType)NextStory))
                        {
                            MoveHeaderFooterFocus(CurrentRange, RedactCommon.FindSectionWithHeaderFooter(CurrentRange.Document, CurrentRange), RedactCommon.GetHeaderFooterType((Word.WdStoryType)NextStory));
                        }
                        return(true);
                    }
                }
                catch (COMException)
                { }

                NextStory++;
            }

            return(false);
        }
Beispiel #7
0
        /// <summary>
        /// Finds the next redaction mark in subsequent document stories of the current type (e.g. subsequent headers).
        /// </summary>
        /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the output mark if one is found.</param>
        /// <returns>True if a mark was found, otherwise False.</returns>
        private static bool FindNextMarkInStoryRanges(ref Word.Range SelectionRange)
        {
            object CollapseStart = Word.WdCollapseDirection.wdCollapseStart;

            Word.WdHeaderFooterIndex?headerFooterId = null;

            while (SelectionRange.NextStoryRange != null)
            {
                //get the header/footer ID
                if (SelectionRange.StoryType != Word.WdStoryType.wdTextFrameStory)
                {
                    headerFooterId = RedactCommon.GetHeaderFooterType(SelectionRange.StoryType);
                }

                SelectionRange = SelectionRange.NextStoryRange;
                SelectionRange.Collapse(ref CollapseStart);
                if (FindNextMarkInCurrentStoryRange(ref SelectionRange))
                {
                    //move the focus in the view
                    if (RedactCommon.IsHeaderFooter(SelectionRange.StoryType))
                    {
                        MoveHeaderFooterFocus(SelectionRange, RedactCommon.FindSectionWithHeaderFooter(SelectionRange.Document, SelectionRange), headerFooterId);
                    }
                    return(true);
                }
            }
            return(false);
        }
Beispiel #8
0
        /// <summary>
        /// Redact all shapes in a range.
        /// </summary>
        /// <param name="StoryRange">A range containing zero or more shapes.</param>
        private void RedactShapes(Word.Range StoryRange)
        {
            if (StoryRange.ShapeRange.Count > 0)
            {
                List <int> ShapeLocations           = new List <int>();
                Dictionary <int, Word.Shape> Shapes = new Dictionary <int, Word.Shape>();

                //scan for shapes
                //since we're messing with the text, we need reorder them back to front by document order (they're in z-order).
                foreach (Word.Shape Shape in StoryRange.ShapeRange)
                {
                    Word.Range AnchorRange = Shape.Anchor;
                    ShapeLocations.Add(AnchorRange.Start);
                    Shapes.Add(AnchorRange.Start, Shape);
                }

                //sort the anchor locations
                ShapeLocations.Sort();

                for (int i = ShapeLocations.Count - 1; i >= 0; i--)
                {
                    Word.Shape Shape = Shapes[ShapeLocations[i]];
                    if (RedactCommon.IsMarkedRange(Shape.Anchor, ShadingColor))
                    {
                        Debug.WriteLine("Shape from " + Shape.Anchor.Start + " to " + Shape.Anchor.End + "to be redacted.");
                        RedactShape(StoryRange.Document, Shape);
                    }
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Redact the current document.
        /// </summary>
        private void RedactDocument()
        {
            object Story = Word.WdUnits.wdStory;

            Word.WdViewType OriginalView = Application.ActiveWindow.View.Type;
            Application.ScreenUpdating = false;

            Word.Document SourceFile = Application.ActiveDocument;

            //cache the document's saved and update styles from template states
            bool Saved        = SourceFile.Saved;
            bool UpdateStyles = SourceFile.UpdateStylesOnOpen;

            //BUG 5819: need to make sure this is off so that Normal's properties don't get demoted into redacted file
            Application.ActiveDocument.UpdateStylesOnOpen = false;
            Word.Document FileToRedact = RedactCommon.CloneDocument(Application.ActiveDocument);

            //reset those states to their cached values
            SourceFile.UpdateStylesOnOpen = UpdateStyles;
            SourceFile.Saved = Saved;

            //get the window for the document surface (_WwG)
            NativeWindow WordWindow = RedactCommon.GetDocumentSurfaceWindow();

            //show progress UI
            using (FormDoRedaction ProgressUI = new FormDoRedaction(FileToRedact, this))
            {
                ProgressUI.ResetProgress();
                DialogResult RedactResult = ProgressUI.ShowDialog();

                //fix the view
                Application.ActiveWindow.View.SeekView = Word.WdSeekView.wdSeekMainDocument;
                Application.ActiveWindow.View.Type     = OriginalView;
                Application.Selection.HomeKey(ref Story, ref Missing);

                if (RedactResult != DialogResult.OK)
                {
                    GenericMessageBox.Show(Resources.RedactionFailed, Resources.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
                }
                else
                {
                    //dialog telling the user we're done
                    using (FormSuccess Success = new FormSuccess(FileToRedact.Application))
                        Success.ShowDialog(WordWindow);
                }
            }

            //set focus back onto the document
            NativeMethods.SetFocus(WordWindow.Handle);

            //we need to release our handle, or Word will crash on close as the CLR tries to do it for us
            WordWindow.ReleaseHandle();
        }
Beispiel #10
0
        /// <summary>
        /// Find and mark all instances of a string in a specific StoryRange.
        /// </summary>
        /// <param name="StoryRange">A Range specifying the story range to search.</param>
        /// <returns>True if a hit was found, False otherwise.</returns>
        private int FindAndMarkInStory(Word.Range StoryRange)
        {
            object    CollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
            RangeData LastHit     = new RangeData();
            int       HitCount    = 0;
            object    Missing     = Type.Missing;
            object    FindText    = textBox.Text;

            //text boxes in headers/footers aren't in the text box story
            if ((int)StoryRange.StoryType > 5 && StoryRange.ShapeRange.Count > 0)
            {
                foreach (Word.Shape Shape in StoryRange.ShapeRange)
                {
                    Word.Range ShapeRange = RedactCommon.RangeFromShape(Shape);
                    if (ShapeRange != null)
                    {
                        HitCount += FindAndMarkInStory(ShapeRange);
                    }
                }
            }

            Word.Find FindScope = StoryRange.Find;

            //set search options
            FindScope.IgnorePunct       = checkBoxIgnorePunct.Checked;
            FindScope.IgnoreSpace       = checkBoxIgnoreWhitespace.Checked;
            FindScope.MatchAllWordForms = checkBoxWordForms.Checked;
            FindScope.MatchCase         = checkBoxMatchCase.Checked;
            FindScope.MatchPrefix       = checkBoxMatchPrefix.Checked;
            FindScope.MatchSoundsLike   = checkBoxSoundsLike.Checked;
            FindScope.MatchSuffix       = checkBoxMatchSuffix.Checked;
            FindScope.MatchWholeWord    = checkBoxWholeWord.Checked;
            FindScope.MatchWildcards    = checkBoxWildcards.Checked;

            while (FindScope.Execute(ref FindText, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing) && (StoryRange.Start != LastHit.Start || StoryRange.End != LastHit.End))
            {
                HitCount++;
                LastHit = new RangeData(StoryRange.Start, StoryRange.End);
                StoryRange.Font.Shading.BackgroundPatternColor = (Word.WdColor)ShadingColor;
                StoryRange.Collapse(ref CollapseEnd);
            }

            //check in any subsequent stories
            if (StoryRange.NextStoryRange != null)
            {
                HitCount += FindAndMarkInStory(StoryRange.NextStoryRange);
            }

            return(HitCount);
        }
Beispiel #11
0
        /// <summary>
        /// Redact an inline shape by replacing it with a 1px image.
        /// </summary>
        /// <param name="InlineShape">The InlineShape to redact.</param>
        private void RedactInlineShape(Word.InlineShape InlineShape)
        {
            string ImagePath = RedactCommon.CreateRedactedImage();

            float Height = InlineShape.Height;
            float Width  = InlineShape.Width;

            InlineShape.Range.Select();
            Application.Selection.Delete(ref Missing, ref Missing);

            Word.InlineShape RedactedImage = Application.Selection.InlineShapes.AddPicture(ImagePath, ref Missing, ref Missing, ref Missing);
            RedactedImage.AlternativeText = string.Empty;
            RedactedImage.Height          = Height;
            RedactedImage.Width           = Width;
        }
Beispiel #12
0
 /// <summary>
 /// Finds the previous redaction mark in current document story (e.g. this or any previous odd page footer).
 /// </summary>
 /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the output mark if one is found.</param>
 /// <returns>True if a mark was found, otherwise False.</returns>
 private static bool FindPreviousMarkInCurrentStory(ref Word.Range CurrentRange)
 {
     //execute the search
     if (FindPreviousMarkInCurrentStoryRange(ref CurrentRange))
     {
         return(true);
     }
     else if (RedactCommon.PreviousStoryRange(CurrentRange) != null)
     {
         return(FindPreviousMarkInStoryRanges(ref CurrentRange));
     }
     else
     {
         return(false);
     }
 }
Beispiel #13
0
        /// <summary>
        /// Redact a shapes by replacing it with a 1px image.
        /// </summary>
        /// <param name="Document">The document containing the shape.</param>
        /// <param name="ShapeToRedact">The shape to redact.</param>
        private void RedactShape(Word.Document Document, Word.Shape ShapeToRedact)
        {
            string ImagePath   = RedactCommon.CreateRedactedImage();
            object AnchorRange = ShapeToRedact.Anchor;
            object Height      = ShapeToRedact.Height;
            object Width       = ShapeToRedact.Width;
            object Top         = ShapeToRedact.Top;
            object Left        = ShapeToRedact.Left;
            object WrapType    = ShapeToRedact.WrapFormat.Type;

            ((Word.Range)AnchorRange).Select();
            ShapeToRedact.Delete();

            Word.Shape RedactedImage = Document.Shapes.AddPicture(ImagePath, ref Missing, ref Missing, ref Left, ref Top, ref Width, ref Height, ref AnchorRange);
            RedactedImage.AlternativeText = string.Empty;
            RedactedImage.WrapFormat.Type = (Word.WdWrapType)WrapType;
        }
Beispiel #14
0
        /// <summary>
        /// Show the Find and Mark dialog box.
        /// </summary>
        private void FindAndMark()
        {
            Word.Range      OriginalSelection = Application.Selection.Range.Duplicate;
            Word.WdViewType OriginalView      = Application.ActiveWindow.View.Type;

            //get the window for the document surface (_WwG)
            NativeWindow WordWindow = RedactCommon.GetDocumentSurfaceWindow();

            using (FormFindAndMark find = new FormFindAndMark(ShadingColor))
            {
                find.ShowDialog(WordWindow);
            }

            //set focus back onto the document
            NativeMethods.SetFocus(WordWindow.Handle);

            //we need to release our handle, or Word will crash on close as the CLR tries to do it for us
            WordWindow.ReleaseHandle();

            Application.ActiveWindow.View.Type = OriginalView;
            OriginalSelection.Select();
        }
Beispiel #15
0
        /// <summary>
        /// Finds the previous redaction mark in previous document stories.
        /// </summary>
        /// <param name="CurrentRange">The range containing the starting point of the search. Changes to the location of the output mark if one is found.</param>
        /// <returns>True if a mark was found, otherwise False.</returns>
        private bool FindPreviousMarkInOtherStory(ref Word.Range CurrentRange)
        {
            //move backward a story
            int PrevStory = (int)CurrentRange.StoryType - 1;

            while (PrevStory >= 1)
            {
                try
                {
                    CurrentRange = CurrentRange.Document.StoryRanges[(Word.WdStoryType)PrevStory];
                    while (CurrentRange.NextStoryRange != null)
                    {
                        CurrentRange = CurrentRange.NextStoryRange;
                    }
                    CurrentRange.Collapse(ref CollapseEnd);
                }
                catch (COMException)
                { }

                if (FindPreviousMarkInCurrentStory(ref CurrentRange))
                {
                    if (RedactCommon.IsHeaderFooter((Word.WdStoryType)PrevStory))
                    {
                        MoveHeaderFooterFocus(CurrentRange, RedactCommon.FindSectionWithHeaderFooter(CurrentRange.Document, CurrentRange), RedactCommon.GetHeaderFooterType((Word.WdStoryType)PrevStory));
                    }
                    else if (CurrentRange.Document.ActiveWindow.View.Type == Word.WdViewType.wdPrintView)
                    {
                        CurrentRange.Document.ActiveWindow.View.SeekView = Word.WdSeekView.wdSeekMainDocument; //set the seek back to the document
                    }
                    return(true);
                }

                PrevStory--;
            }

            return(false);
        }
Beispiel #16
0
        /// <summary>
        /// Redact a range, replacing all marked text.
        /// </summary>
        /// <param name="range">A range to redact.</param>
        /// <param name="rangeData">A RangeDataEx containing properties about the range.</param>
        private void RedactRange(Word.Range range, RangeDataEx rangeData)
        {
            object ParagraphNumber = Word.WdNumberType.wdNumberParagraph;

            foreach (Word.Paragraph p in range.Paragraphs)
            {
                //get the para's range
                Word.Range ParagraphRange = p.Range;

                //trim to the selection, if needed
                if (range.Start > ParagraphRange.Start)
                {
                    ParagraphRange.Start = range.Start;
                }
                if (range.End < ParagraphRange.End)
                {
                    ParagraphRange.End = range.End;
                }
                if (ParagraphRange.End == p.Range.End - 1 && ParagraphRange.Start == p.Range.Start)
                {
                    p.Range.ListFormat.ConvertNumbersToText(ref ParagraphNumber); //if the whole para was redacted, redact the numbering
                }
                //make it black on black
                ParagraphRange.Font.Shading.BackgroundPatternColor = Word.WdColor.wdColorAutomatic;
                ParagraphRange.HighlightColorIndex = Word.WdColorIndex.wdBlack; //moving to highlighting instead of text background
                ParagraphRange.Font.Color          = Word.WdColor.wdColorBlack;

                //get rid of links and bookmarks
                foreach (Word.Hyperlink Hyperlink in ParagraphRange.Hyperlinks)
                {
                    Hyperlink.Delete();
                }
                foreach (Word.Bookmark Bookmark in ParagraphRange.Bookmarks)
                {
                    Bookmark.Delete();
                }

                //BUG 110: suppress proofing errors
                ParagraphRange.NoProofing = -1;

                //finally, replace the text
                Debug.Assert(ParagraphRange.ShapeRange.Count == 0, "Some Shapes were not redacted by RedactShapes.");
                if (ParagraphRange.InlineShapes.Count > 0)
                {
                    //if there are images, then split into subranges and process text and images separately
                    List <RangeData> Subranges = RedactCommon.SplitRange(ParagraphRange);

                    for (int j = Subranges.Count - 1; j >= 0; j--)
                    {
                        //set start and end
                        ParagraphRange.Start = Subranges[j].Start;
                        ParagraphRange.End   = Subranges[j].End;

                        if (ParagraphRange.InlineShapes.Count > 0)
                        {
                            RedactInlineShape(ParagraphRange.InlineShapes[1]);
                        }
                        else
                        {
                            ParagraphRange.Text = RedactCommon.BuildFillerText(ParagraphRange, rangeData);
                        }
                    }
                }
                else
                {
                    ParagraphRange.Text = RedactCommon.BuildFillerText(ParagraphRange, rangeData);
                }
            }
        }
Beispiel #17
0
        /// <summary>
        /// Redact a story range (e.g. all textboxes, all first page headers, the main document).
        /// </summary>
        /// <param name="StoryRange">The story range to redact.</param>
        /// <param name="Worker">A BackgroundWorker on which to report progress.</param>
        private void RedactStoryRange(Word.Range StoryRange, BackgroundWorker Worker)
        {
            //textboxes in headers/footers/textboxes/etc. are not in the textbox story.
            if ((int)StoryRange.StoryType > 5 && StoryRange.ShapeRange.Count > 0)
            {
                foreach (Word.Shape Shape in StoryRange.ShapeRange)
                {
                    Word.Range ShapeRange = RedactCommon.RangeFromShape(Shape);
                    if (ShapeRange != null)
                    {
                        RedactStoryRange(ShapeRange, Worker);
                    }
                }
            }

            //redact all shapes
            RedactShapes(StoryRange);

            //remove redaction marks from all paragraph mark glyphs
            Word.Range ParaMark = StoryRange.Duplicate;
            foreach (Word.Paragraph Paragraph in StoryRange.Paragraphs)
            {
                //select the para mark
                ParaMark.Start = Paragraph.Range.End - 1;
                ParaMark.End   = ParaMark.Start + 1;

                if (RedactCommon.IsMarkedRange(ParaMark, ShadingColor))
                {
                    ParaMark.Font.Shading.BackgroundPatternColor = Word.WdColor.wdColorAutomatic;
                }
            }

            //run through the collection
            //since we're messing with the text, we need to go back to front.
            StoryRange.Collapse(ref CollapseEnd);
            while (FindPreviousMarkInCurrentStoryRange(ref StoryRange))
            {
                Debug.Assert(StoryRange.Paragraphs.Count < 2, "redacting more than one paragraph - Selection.Move will behave incorrectly in tables");

                //break up the range by formatting, so we can maintain layout through the redaction
                List <RangeDataEx> RangeMarkers = RedactCommon.GetAllMarkedRanges(StoryRange, ShadingColor, false);
                for (int i = RangeMarkers.Count - 1; i >= 0; i--)
                {
                    StoryRange.Start = RangeMarkers[i].Start;
                    StoryRange.End   = RangeMarkers[i].End;

                    RedactRange(StoryRange, RangeMarkers[i]);

                    //update progress UI
                    if (Worker != null && StoryRange.StoryType == Word.WdStoryType.wdMainTextStory)
                    {
                        Worker.ReportProgress((((StoryRange.StoryLength - RangeMarkers[i].Start) * 100) / StoryRange.StoryLength), null);
                    }
                }
            }

            //get all other stories
            if (StoryRange.NextStoryRange != null)
            {
                RedactStoryRange(StoryRange.NextStoryRange, Worker);
            }

            //give each non-main story a small % of the progress bar
            if (Worker != null && StoryRange.StoryType != Word.WdStoryType.wdMainTextStory)
            {
                Worker.ReportProgress(100 + (int)StoryRange.StoryType);
            }
        }