Image Repeater

I’m currently building a system that allows a user to target a bookmark in a template word doc and associate certain data points from our system with it. To accomplish this, I’m simply iterating over the bookmarks collection and seeing if I have an association for each bookmark stored in our database. One of the data points we need to show is a series of images. The end user wants the images displayed in a two by two grid (4 on each page) with optional captions for each image.

What’s the best way to accomplish this? One idea we were kicking around is dynamically building a table and just adding the images to the cells as we add them…


Hi Jason,
Thanks for your request. Yes, I think, building a table would be the best and easiest way to achieve this.
Also, you can use PageBreakBefore option to control page breaks:
Best regards,

The table builder is working really well, except for one minor problem. In the system we are building, the user will be able to specify the number of columns and the number of rows per page. I’m currently determining the size of the page, then dividing that up depending on number of columns or rows. So for example, if the user wants two rows per page, each image should have a height of roughly half the page height.
I have this working pretty well until headers and footers are thrown into the mix. At that point the height is off and the images no longer fit the page correctly. Here’s the code I’m using:

double containerWidth = section.PageSetup.PageWidth -
    section.PageSetup.LeftMargin -
    section.PageSetup.RightMargin -
    section.PageSetup.Borders.Left.DistanceFromText -
    section.PageSetup.Borders.Right.DistanceFromText -
    section.PageSetup.Borders.Left.LineWidth -

double containerHeight = section.PageSetup.PageHeight -
    section.PageSetup.TopMargin -
    section.PageSetup.BottomMargin -
    section.PageSetup.Borders.Top.DistanceFromText -
    section.PageSetup.Borders.Bottom.DistanceFromText -
    section.PageSetup.Borders.Top.LineWidth -
    section.PageSetup.Borders.Bottom.LineWidth -
    section.PageSetup.HeaderDistance -

double cellSpacing = 5;
int numColumns = node.PhotoGrid_NumberColumns ?? 2;
int numRows = node.PhotoGrid_NumberRows ?? 2;
double cellWidth = (containerWidth - (cellSpacing numColumns)) / numColumns;
double cellHeight = (containerHeight - (cellSpacing numRows)) / numRows;

Based on some other forum posts, you can see I tried throwing the header and footer distance in, but that doesn’t seem to change when I add more content to the header. Any suggestions on how to get the actual header and footer height? Or is there a cleaner way to get the usable height and width of a page?


Hi Jason,
Thanks for your inquiry.
I’m afraid there is no direct way to find the current height of a header or footer or the amount of free space on a page. However you can use the work around code below to calculate the actual size of a header or footer. Please note that you will need Aspose.Words version 9.7 or above for one of the required methods. I have attached the main class to this post.

// Calcuate the height of the header in points.
double headerHeight = CalculateHeightOfHeaderFooterInPoints(doc.FirstSection.HeadersFooters[HeaderFooterType.HeaderPrimary]);
// As a test, add a line to show this position in the document.
Shape lineShape = new Shape(doc, ShapeType.Line);
lineShape.StrokeColor = Color.Red;
lineShape.StrokeWeight = 2;
lineShape.Width = doc.FirstSection.PageSetup.PageWidth;
lineShape.RelativeHorizontalPosition = RelativeHorizontalPosition.Page;
lineShape.RelativeVerticalPosition = RelativeVerticalPosition.Page;
lineShape.Left = 0;
lineShape.Top = headerHeight;
/// Calcuates the actual height of a header or footer, seen as the maximum of either the top margin or
/// the height of the content within the header or footer.
public static double CalculateHeightOfHeaderFooterInPoints(HeaderFooter headerFooter)
    // Add a bit of extra space in points.
    const double buffer = 5;
    PageSetup ps = headerFooter.ParentSection.PageSetup;
    // Distance from the edge of the page based on whether the input is a header or footer.
    double distanceFromPage = headerFooter.IsHeader ? ps.HeaderDistance : ps.FooterDistance;
    // Render the header or footer to image.
    Image nodeImage = RenderNode(headerFooter, null);
    // Calcuate the height of the header or footer.
    double headerHeight = ConvertUtil.PixelToPoint(nodeImage.Height) + distanceFromPage + buffer;
    return Math.Max(headerHeight, ps.TopMargin);


Thanks for the reply.
This looks like it will do exactly what I need, but I can’t find either the Image class, or the RenderNode method. What namespace are they in?

I’m pretty sure I’m using words 10.1 (for .NET)

Thanks for your request. Please see the attachment to the Adam’s post.
Best regards,

I had a little trouble with the code provided, but thankfully I didn’t actually need the part that wasn’t working. Below is what I ended up with. You guys are great, thanks!

public double CalculateHeightOfHeaderFooterInPoints(HeaderFooter headerFooter)
	// Add a bit of extra space in points.
	const double buffer = 5;
	PageSetup ps = headerFooter.ParentSection.PageSetup;
	// Distance from the edge of the page based on whether the input is a header or footer.
	double distanceFromPage = headerFooter.IsHeader ? ps.HeaderDistance : ps.FooterDistance;
	// Calcuate the height of the header or footer.
	double headerHeight = ConvertUtil.PixelToPoint(GetNodeHeight(headerFooter)  ) + distanceFromPage + buffer;
	return Math.Max(headerHeight, ps.TopMargin);
public Double GetNodeHeight(Node node)
	// Run some argument checks.
	if (node == null)
		throw new ArgumentException("Node cannot be null");
	var	imageOptions = new Aspose.Words.Saving.ImageSaveOptions(SaveFormat.Png);
	// Store the paper color to be used on the final image and change to transparent.
	// This will cause any content around the rendered node to be removed later on.
	Color savePaperColor = imageOptions.PaperColor;
	imageOptions.PaperColor = Color.Transparent;
	// There a bug which affects the cache of a cloned node. To avoid this we instead clone the entire document including all nodes,
	// find the matching node in the cloned document and render that instead.
	Document doc = (Document)node.Document.Clone(true);
	node = doc.GetChild(NodeType.Any, node.Document.GetChildNodes(NodeType.Any, true).IndexOf(node), true);
	// Create a temporary shape to store the target node in. This shape will be rendered to retrieve
	// the rendered content of the node.
	Aspose.Words.Drawing.Shape shape = new Aspose.Words.Drawing.Shape(doc, Aspose.Words.Drawing.ShapeType.TextBox);
	Section parentSection = (Section)node.GetAncestor(NodeType.Section);
	// Assume that the node cannot be larger than the page in size.
	shape.Width = parentSection.PageSetup.PageWidth;
	shape.Height = parentSection.PageSetup.PageHeight;
	shape.FillColor = Color.Transparent; // We must make the shape and paper color transparent.
	// Don't draw a surronding line on the shape.
	shape.Stroked = false;
	Node currentNode = node;
	// If the node contains block level nodes then just add a copy of these nodes to the shape.
	if (currentNode is InlineStory || currentNode is Story)
		CompositeNode composite = (CompositeNode)currentNode;
		foreach (Node childNode in composite.ChildNodes)
		// Move up through the DOM until we find node which is suitable to insert into a Shape (a node with a parent can contain paragraph, tables the same as a shape).
		// Each parent node is cloned on the way up so even a descendant node passed to this method can be rendered. 
		// Since we are working with the actual nodes of the document we need to clone the target node into the temporary shape.
		while (!(currentNode.ParentNode is InlineStory || currentNode.ParentNode is Story || currentNode.ParentNode is Aspose.Words.Drawing.ShapeBase || currentNode.NodeType == NodeType.Paragraph))
			CompositeNode parent = (CompositeNode)currentNode.ParentNode.Clone(false);
			currentNode = currentNode.ParentNode;
			node = parent; // Store this new node to be inserted into the shape.
		// Add the node to the shape.
	// We must add the shape to the document tree to have it rendered.
	// Render the shape to stream so we can take advantage of the effects of the ImageSaveOptions class.
	// Retrieve the rendered image and remove the shape from the document.
	MemoryStream stream = new MemoryStream();
	shape.GetShapeRenderer().Save(stream, imageOptions);
	double nodeHeight = 0;
	// Load the image into a new bitmap.
	using (Bitmap renderedImage = new Bitmap(stream))
		// Extract the actual content of the image by cropping transparent space around
		// the rendered shape.
		Rectangle cropRectangle = FindBoundingBoxAroundNode(renderedImage);
		nodeHeight = cropRectangle.Height;
	return nodeHeight;

Hi there,
It’s great it’s working for you. If you have any further queries, please feel free to ask.