Convert Table to Image

Dear,

I’m using Aspose.Words Java and would like to understand how I can save the table nodes from my document as images.
I have seen that for OfficeMath elements, there is a OfficeMathRenderer object which can do exactly that.

I could not find a similar TableNodeRenderer object.

How can I render the table node to an Image.

Many thanks for providing assistance with this.

Kind Regards

Patrick Vanbrabant

@PatrickVB,

We have logged your requirement in our issue tracking system. The ID of this issue is WORDSNET-15920. Our product team will further look into the details of this problem and we will keep you updated on the status of this issue. We apologize for any inconvenience.

@PatrickVB,

Regarding WORDSNET-15920, our product team has decided that they would not add «render methods» for each type of Node available in Aspose.Words’ API. Instead, you can use the following method to meet this requirement.

Document doc = new Document("D:\\temp\\in.docx");
Table tab = doc.getFirstSection().getBody().getTables().get(0);
RenderNode(tab, "D:\\temp\\out.png", new ImageSaveOptions(SaveFormat.PNG));

The following method renders any node in a document to the path specified using the image save options.
node: The node to render.
path: The path to save the rendered image to.
imageOptions: The image options to use during rendering. This can be null.

public static void RenderNode(Node node, String filePath, ImageSaveOptions imageOptions) throws Exception
{
    // Run some argument checks.
    if (node == null)
        throw new IllegalArgumentException("Node cannot be null");

    // If no image options are supplied, create default options.
    if (imageOptions == null)
        imageOptions = new ImageSaveOptions(FileFormatUtil.extensionToSaveFormat((filePath.split("\\.")[filePath.split("\\.").length - 1])));

    // 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.getPaperColor();
    //imageOptions.PaperColor = Color.Transparent;
    imageOptions.setPaperColor(new Color(0, 0, 0, 0));
    // 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.getDocument().deepClone(true);
    node = doc.getChild(NodeType.ANY, node.getDocument().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.
    Shape shape = new Shape(doc, ShapeType.TEXT_BOX);
    Section parentSection = (Section)node.getAncestor(NodeType.SECTION);

    // Assume that the node cannot be larger than the page in size.
    shape.setWidth(parentSection.getPageSetup().getPageWidth());
    shape.setHeight(parentSection.getPageSetup().getPageHeight());
    shape.setFillColor(new Color(0, 0, 0, 0)); // We must make the shape and paper color transparent.

    // Don't draw a surrounding line on the shape.
    shape.setStroked(false);

    // 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.
    Node currentNode = node;
    while (!(currentNode.getParentNode() instanceof InlineStory
            || currentNode.getParentNode() instanceof Story
            || currentNode.getParentNode() instanceof ShapeBase))
    {
        CompositeNode parent = (CompositeNode)currentNode.getParentNode().deepClone(false);
        currentNode = currentNode.getParentNode();
        parent.appendChild(node.deepClone(true));
        node = parent; // Store this new node to be inserted into the shape.
    }

    // We must add the shape to the document tree to have it rendered.
    shape.appendChild(node.deepClone(true));
    parentSection.getBody().getFirstParagraph().appendChild(shape);

    // 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.
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    shape.getShapeRenderer().save(stream, imageOptions);
    shape.remove();

    // Load the image into a new bitmap.
    BufferedImage renderedImage = ImageIO.read(new ByteArrayInputStream(stream.toByteArray()));

    // Extract the actual content of the image by cropping transparent space around
    // the rendered shape.
    Rectangle cropRectangle = FindBoundingBoxAroundNode(renderedImage);

    BufferedImage croppedImage = new BufferedImage(cropRectangle.width, cropRectangle.height, BufferedImage.TYPE_INT_RGB);

    // Create the final image with the proper background color.
    Graphics2D g = croppedImage.createGraphics();
    g.setBackground(savePaperColor);
    g.clearRect(0, 0, croppedImage.getWidth(), croppedImage.getHeight());
    g.drawImage(renderedImage,
            0, 0, croppedImage.getWidth(), croppedImage.getHeight(),
            cropRectangle.x, cropRectangle.y, cropRectangle.x + cropRectangle.width, cropRectangle.y + cropRectangle.height,
            null);

    ImageIO.write(croppedImage, "png", new File(filePath));
}

public static Rectangle FindBoundingBoxAroundNode(BufferedImage originalBitmap)
{
    Point min = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
    Point max = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);

    for (int x = 0; x < originalBitmap.getWidth(); ++x)
    {
        for (int y = 0; y < originalBitmap.getHeight(); ++y)
        {
            // For each pixel that is not transparent calculate the bounding box around it.
            if (originalBitmap.getRGB(x, y) != 0)
            {
                min.x = Math.min(x, min.x);
                min.y = Math.min(y, min.y);
                max.x = Math.max(x, max.x);
                max.y = Math.max(y, max.y);
            }
        }
    }

    // Add one pixel to the width and height to avoid clipping.
    return new Rectangle(min.x, min.y, (max.x - min.x) + 1, (max.y - min.y) + 1);
}

Hope, this helps.