Fix/Calculate Header Footer Height while Replacing Footer Content in Word DOCX ODT Documents Dynamically using C# .NET

Hi,
I have a collection of documents which have different footer content.
I want to replace the footer content with a single line. This works for me however it affects the content as the page content is slightly different in terms of height.

I would like to be able to do the following

  1. Get original footer height.
  2. Remove footer.
  3. Add new footer content.
  4. Fix footer height to the original size.

Currently my code doesn’t work

  1. ??
  2. private static void ClearFooter(Aspose.Words.Document doc)
    {
         foreach (Section section in doc)
         {
             foreach (HeaderFooter footer in section.HeadersFooters)
             {
                 if (footer.HeaderFooterType == HeaderFooterType.FooterPrimary ||
                     footer.HeaderFooterType == HeaderFooterType.FooterFirst ||
                     footer.HeaderFooterType == HeaderFooterType.FooterEven)
                 {
                     footer.Remove();
                 }
             }
         }
     }
    
  3. private static void InsertWordFooter(Document doc, string user, string status)
     {
         DocumentBuilder builder = new DocumentBuilder(doc);
         Section currentSection = builder.CurrentSection;
         Aspose.Words.PageSetup pageSetup = currentSection.PageSetup;
         pageSetup.DifferentFirstPageHeaderFooter = false;
         builder.MoveToHeaderFooter(Aspose.Words.HeaderFooterType.FooterPrimary);
         builder.Writeln("[somtext]");
    
         CopyHeadersFootersFromPreviousSection(currentSection);
         //TODO Set the footerheight to the original height...
     }
    
  4. ??

@RCAtWork,

To ensure a timely and accurate response, please ZIP and attach the following resources here for testing:

  • Your simplified input Word document(s)
  • Aspose.Words 19.11 generated output DOCX file showing the undesired behavior
  • Your expected DOCX file showing the desired output. You can create it by using MS Word.

As soon as you get these pieces of information ready, we will start investigation into your scenario and provide you code to achieve the same by using Aspose.Words. Thanks for your cooperation.

1 Like

Hi Awais,

Here is a picture (image.png (14.6 KB)) of the input and the incorrect output.

Here is a picture (image.png (111.1 KB)) of the desired input beside the desired output

I’ve also attached (aspose forum.zip (96.1 KB)) the 3 files.

Please note - I will be doing this for hundreds of documents. Sometimes the footers will differ in height. But the output needs to keep the footer the same height.

I’ve tried pageSetup.FooterDistance however the value before and after I make a change to the footer content doesn’t alter the number as I expected it to.

This is my last hurdle before I can purchase the licence which is needed for a production issue. I would be delighted if you could help.

@RCAtWork,

You can build logic on the following draft code to meet this requirement:

public static void MarkHeaderFooterBoundaries(LayoutEnumerator enumerator)
{
    do
    {
        if (enumerator.MoveLastChild())
        {
            MarkHeaderFooterBoundaries(enumerator);
            enumerator.MoveParent();
        }

        if (enumerator.Type == LayoutEntityType.HeaderFooter)
        {
            AddLine(enumerator);
        }

        // Stop after all elements on the page have been procesed.
        if (enumerator.Type == LayoutEntityType.Page)
            return;

    } while (enumerator.MovePrevious());
}

public static void AddLine(LayoutEnumerator enumerator)
{
    double top = 0;

    DocumentBuilder builder = new DocumentBuilder(enumerator.Document);
    if (enumerator.Kind.Equals("FIRSTPAGEFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterFirst);
        top = enumerator.Rectangle.Top;
    }
    else if (enumerator.Kind.Equals("PRIMARYFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterPrimary);
        top = enumerator.Rectangle.Top;
    }
    else if (enumerator.Kind.Equals("EVENPAGESFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterEven);
        top = enumerator.Rectangle.Top;
    }

    Shape line = new Shape(enumerator.Document, ShapeType.Line);
    line.StrokeColor = Color.Red;
    line.RelativeHorizontalPosition = RelativeHorizontalPosition.Page;
    line.RelativeVerticalPosition = RelativeVerticalPosition.Page;
    line.WrapType = WrapType.None;
    line.AlternativeText = "RemoveMe";

    builder.InsertNode(line);

    line.Width = enumerator.Rectangle.Width;
    line.Left = 0;
    line.Top = top;
} 

Document doc = new Document("E:\\temp\\aspose forum\\My Input.docx");

// To Get Height of Original Footer

LayoutEnumerator enumerator = new LayoutEnumerator(doc);

for (int pageIndex = 0; pageIndex < doc.PageCount; pageIndex++)
{
    MarkHeaderFooterBoundaries(enumerator);
    enumerator.MoveNext();
}

doc.UpdatePageLayout();

PageSetup pageSetup = null;
double top = 0;
double height = 0;
foreach (Shape line in doc.GetChildNodes(NodeType.Shape, true))
{
    if (line.AlternativeText.Equals("RemoveMe"))
    {
        if (line.Top > 0)
        {
            PageSetup ps = line.ParentParagraph.ParentSection.PageSetup;
            Console.WriteLine("Footer's Top: " + line.Top + " and Height: " + (ps.PageHeight - line.Top) + " points");

            pageSetup = ps;
            top = line.Top;
            height = ps.PageHeight - line.Top;
            // lets not process other footers at the moment
            break;
        }
    }
}

// Remove Footer along with Temporary ‘RemoveMe’ Type Shapes

HeaderFooter footer = doc.FirstSection.HeadersFooters[HeaderFooterType.FooterPrimary];
footer.RemoveAllChildren();

// Add New Footer Content

// this will disturb the top footer boundary
// but we will add content inside textbox to avoid this problem
DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveToHeaderFooter(HeaderFooterType.FooterPrimary);
builder.Font.Size = 1; // small enough so that it does not effect footer's top boundary
builder.Write("");
// create a textbox that will hold our new content
Shape textBox = new Shape(doc, ShapeType.TextBox);
if (pageSetup != null)
{
    textBox.Width = pageSetup.PageWidth - pageSetup.LeftMargin - pageSetup.RightMargin;
    textBox.Height = height;
    // just to visualize our working area
    textBox.StrokeColor = Color.Green;
    textBox.StrokeWeight = 1;

    textBox.AppendChild(new Paragraph(doc));
    builder.MoveTo(textBox.FirstParagraph);
    builder.Font.Size = 12;
    builder.Writeln("Lets write some New Content");
    builder.Writeln("more Content");
    // textbox can contain Tables and other elements

    footer.FirstParagraph.AppendChild(textBox);
}

// Adjust Footer Height to the Original Size

doc.FirstSection.PageSetup.FooterDistance = height;
doc.Save("E:\\temp\\aspose forum\\19.11.docx");

Hope, this helps in achieving what you are looking for.

1 Like

Thanks Awais,
I found a different route which I will stick to for now:

private void InsertFooterWatermark(Aspose.Words.Document doc, string text, string statusName, string userName, string timezoneId)
    {
        DocumentBuilder builder = new DocumentBuilder(doc);
        Section currentSection = builder.CurrentSection;
        Aspose.Words.PageSetup pageSetup = currentSection.PageSetup;
      
        pageSetup.DifferentFirstPageHeaderFooter = false;

        builder.MoveToHeaderFooter(Aspose.Words.HeaderFooterType.FooterPrimary);

        Shape footerBox = new Shape(doc, ShapeType.TextBox);
        footerBox.Name = "Footer_Box";
        footerBox.Width = pageSetup.PageWidth;
        footerBox.Height = 80; //this doesn't seem to do much - except when its too small it causes issues
        footerBox.RelativeVerticalPosition = RelativeVerticalPosition.BottomMargin;
        footerBox.RelativeHorizontalPosition = RelativeHorizontalPosition.LeftMargin;
        footerBox.VerticalAlignment = VerticalAlignment.Top;
        footerBox.HorizontalAlignment = HorizontalAlignment.Left;
        footerBox.WrapType = WrapType.None;
        footerBox.BehindText = false;
        footerBox.FillColor = System.Drawing.Color.White;
        footerBox.Filled = true;
        footerBox.Stroked = false;
        Paragraph footerParagraph = new Paragraph(doc);
        footerParagraph.ParagraphFormat.Alignment = ParagraphAlignment.Center;
        footerBox.AppendChild(footerParagraph);
        Run footerText = new Run(doc, $"{text}");
        footerText.Font.Size = 8;
        footerText.Font.Color = System.Drawing.Color.DimGray;
        footerText.Font.Name = "Tahoma";
        footerParagraph.AppendChild(footerText);
        builder.InsertNode(footerBox);

        CopyHeadersFootersFromPreviousSection(currentSection);
    }

So far it works well. Its essentially a box over the current footer and seems to keep the same height.

@RCAtWork,

Thanks for the additional information. It is great that you were able to find what you were looking for. Please let us know any time you have any further queries.

It turns out that my method works well for DOCX files (as far as I can tell) - however it ruins (legacy) DOC files. Visually it looks fine. However if I then use that altered file and I make a minor change anywhere it affects the doc. When I reopen it it will bring the footer shape up to the header. I will look to integrate your approach now and let you know how it goes.

@RCAtWork,

Sure, please let us know if you have any troubles and we will be glad to look into this further for you.

1 Like

Thanks - working through it now. Its working out sometimes. I’m now trying to fix it for all pages. There are documents with different footer sections. Its working out for “footer section 1”, but doesn’t replace “footer section 2” onwards. Any ideas where I can change your code to fix this?

I reckon its got something to do with your comment “lets not process other footers at the moment”

@RCAtWork,

We are working on your query and will get back to you soon.

1 Like

Thanks Awais, I really appreciate it.

@RCAtWork,

Please check if the following code is acceptable for you?

Document doc = new Document("E:\\Temp\\in.docx");

LayoutEnumerator enumerator = new LayoutEnumerator(doc);

for (int pageIndex = 0; pageIndex < doc.PageCount; pageIndex++)
{
    MarkHeaderFooterBoundaries(enumerator);
    enumerator.MoveNext();
}

doc.UpdatePageLayout();

Node[] lines = doc.GetChildNodes(NodeType.Shape, true).ToArray();
foreach (Shape line in lines)
{
    if (line.AlternativeText.Equals("RemoveMe"))
    {
        PageSetup pageSetup = line.ParentParagraph.ParentSection.PageSetup;
        Console.WriteLine("Footer's Top: " + line.Top + " and Height: " + (pageSetup.PageHeight - line.Top) + " points");

        HeaderFooter footer = (HeaderFooter)line.GetAncestor(NodeType.HeaderFooter);
        foreach (Run run in footer.GetChildNodes(NodeType.Run, true))
        {
            run.Remove();
        }

        Shape textBox = new Shape(doc, ShapeType.TextBox);
        textBox.Width = pageSetup.PageWidth - pageSetup.LeftMargin - pageSetup.RightMargin;
        textBox.Height = pageSetup.PageHeight - line.Top;
        // just to visualize our working area
        textBox.StrokeColor = Color.Green;
        textBox.StrokeWeight = 1;
        textBox.AppendChild(new Paragraph(doc));
        footer.FirstParagraph.AppendChild(textBox);
    }
    line.Remove();
}

doc.Save("E:\\Temp\\19.11.docx"); 

public static void MarkHeaderFooterBoundaries(LayoutEnumerator enumerator)
{
    do
    {
        if (enumerator.MoveLastChild())
        {
            MarkHeaderFooterBoundaries(enumerator);
            enumerator.MoveParent();
        }

        if (enumerator.Type == LayoutEntityType.HeaderFooter)
        {
            AddLine(enumerator);
        }

        // Stop after all elements on the page have been procesed.
        if (enumerator.Type == LayoutEntityType.Page)
            return;

    } while (enumerator.MovePrevious());
}

public static void AddLine(LayoutEnumerator enumerator)
{
    double top = 0;

    DocumentBuilder builder = new DocumentBuilder(enumerator.Document);
    if (enumerator.Kind.Equals("FIRSTPAGEFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterFirst);
        top = enumerator.Rectangle.Top;
    }
    else if (enumerator.Kind.Equals("PRIMARYFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterPrimary);
        top = enumerator.Rectangle.Top;
    }
    else if (enumerator.Kind.Equals("EVENPAGESFOOTER"))
    {
        builder.MoveToHeaderFooter(HeaderFooterType.FooterEven);
        top = enumerator.Rectangle.Top;
    }
    else
    {
        return;
    }

    HeaderFooter hf = (HeaderFooter)builder.CurrentStory;
    bool shapeAlreadyExists = false;
    foreach (Shape obj in hf.GetChildNodes(NodeType.Shape, true))
    {
        if (obj.AlternativeText.Equals("RemoveMe"))
        {
            shapeAlreadyExists = true;
            break;
        }
    }

    if (!shapeAlreadyExists)
    {
        Shape line = new Shape(enumerator.Document, ShapeType.Line);
        line.StrokeColor = Color.Red;
        line.RelativeHorizontalPosition = RelativeHorizontalPosition.Page;
        line.RelativeVerticalPosition = RelativeVerticalPosition.Page;
        line.WrapType = WrapType.None;
        line.AlternativeText = "RemoveMe";

        builder.InsertNode(line);

        line.Width = enumerator.Rectangle.Width;
        line.Left = 0;
        line.Top = top;
    }
}

Hi Awais,
The above changes don’t have the expected output. See attached simplified input. Note your code output only affects the first footer section.
Input and Output.zip (48.4 KB)

@RCAtWork,

The code is not able to process this document correctly because not all Sections have their own headers/footers. Some Sections have their ‘Link to Previous’ setting enabled. I think, you can modify/edit the source Word document i.e. disable ‘Link to Previous’ option and copy real headers/footers into those Sections. The code should then work fine. Please let me know if we can be of any further assistance.

Oh that’s unfortunate because it seems so close to being doable. I say this because If I remove the IF (!shapeAlreadyExists) I can see the output of every footer’s top and height on every page. Could I add this to a list with the top and height and page and then loop through every page and modify every footer. I know its not the best approach but it would help. Unfortunately I can’t remove the link to previous as these are controlled documents and we are only allowed to replace the footers.

@RCAtWork,

We are checking this scenario and will get back to you soon.

1 Like

Thanks Awais, I’m getting no where fast and this is a big issue for us. The reason I need the footer heights exactly is because the documents are large and have a lot of pictures in them. If I clear and set the footers and keep them to the same size what end up happening is that some pictures are clipped by the footer insead of going to the next page.

If you have any other alternatives I’d be delighted to try them.

@RCAtWork,

Please spare us some time for the investigation of this scenario. We will get back to you with our findings soon.

1 Like

Hi just wondering how you are getting on with this issue. It would be fantastic for us to get a solution.

If it makes things simpler I only need to replace the footer with text. Just as long as the footer height on each page doesn’t change.