PDF | Text box on rotated page

I am attempting to provide a system which will add text boxes onto an existing PDF document which may have page rotations. This is proving far more difficult that I expected.
Box is specified by a position, height, width, font size, font style, background color, border color, and the text itself.

Text Stamp positions well even on a rotate page but the text aligns to the bottom of the rectangle. Also there doesn’t seem to be support for a background or border. I was able to add them with a graph and rectangle but they are higher in the z-index so they obscure the text. Text Stamp doesn’t appear to offer a z-index parameter.

A FloatingBox properly displays the text aligned top/left. The background doesn’t support alpha channel so I can only get solid colors. This is resolved with a graph and rectangle which properly accepts alpha channel colors. But the box is not reliably placed on a rotate page with some boxes being correct while others are wrong with no logical pattern.

Can you point me to the right direction to resolve this.
PS we have a Aspose.Total license so we would be able to use features from the other packages.

@srmorley

It sounds like you’re facing a few challenges with adding text boxes to a PDF document that may have rotated pages. Here are some suggestions to help you resolve these issues using Aspose.PDF and possibly other components from Aspose.Total:

1. Handling Page Rotation

To correctly position your text boxes on rotated pages, you need to take the page rotation into account when calculating the position of your text boxes. You can retrieve the rotation of a page using the Page.Rotate property and adjust your coordinates accordingly.

Here’s a basic example of how to adjust the position based on rotation:

var pdfDocument = new Document("input.pdf");
var page = pdfDocument.Pages[1]; // Get the first page

// Get the rotation angle
int rotation = page.Rotate;

// Adjust your coordinates based on the rotation
float adjustedX = originalX;
float adjustedY = originalY;

if (rotation == 90)
{
    adjustedX = originalY; // Swap X and Y
    adjustedY = page.Rect.Height - originalX; // Adjust Y based on height
}
else if (rotation == 180)
{
    adjustedX = page.Rect.Width - originalX;
    adjustedY = page.Rect.Height - originalY;
}
else if (rotation == 270)
{
    adjustedX = page.Rect.Width - originalY;
    adjustedY = originalX;
}

// Now use adjustedX and adjustedY to place your text box

2. Text Alignment

If you want to align the text to the top-left of the text box, you can use the TextStamp class and set its properties accordingly. However, if the text is aligning to the bottom, you may need to adjust the Y position of the text manually based on the height of the text box.

3. Background and Border

To add a background and border to your text box, you can use the Graph and Rectangle classes. To ensure that the text appears above the background, you can draw the rectangle first and then add the text stamp afterward. Here’s an example:

var graph = new Graph(page.Rect.Width, page.Rect.Height);
var rect = new Rectangle(adjustedX, adjustedY, adjustedX + width, adjustedY + height);

// Draw the background rectangle
graph.DrawRectangle(rect, new Color(255, 0, 0, 128)); // Example with alpha channel

// Add the graph to the page
page.Paragraphs.Add(graph);

// Create a TextStamp
var textStamp = new TextStamp("Your Text Here")
{
    Position = new Position(adjustedX, adjustedY + height), // Adjust Y to align top
    TextAlignment = HorizontalAlignment.Left,
    // Set other properties like font size, style, etc.
};

// Add the text stamp to the page
page.AddStamp(textStamp);

4. Using Aspose.Words or Other Packages

If you find that Aspose.PDF is not meeting your needs, consider using Aspose.Words for PDF manipulation. It has more advanced features for handling text and layout, and you can convert documents to PDF format while maintaining the layout.

5. Debugging Positioning Issues

If some boxes are not positioned correctly, ensure that you are consistently applying the same logic for all boxes. You might want to log the calculated positions and the rotation values to identify any discrepancies.

Conclusion

By taking into account the page rotation, adjusting the text alignment, and layering your graphics correctly, you should be able to achieve the desired outcome. If you continue to face issues, consider reaching out to Aspose support for more tailored assistance, especially since you have a license for their products.

I can get close using TextStamp as it seems to position correctly but the text is always at the bottom of the rectangle. Is there a way to control this?
image.png (17.4 KB)

Alternatively using a FloatingBox gives very strange results. (Note page is rotated 180).
image.png (19.1 KB)

What is odd is that the backing Graph and Rectangle don’t show properly in two cases and the text doesn’t show properly in the other 3. No idea why some of the items are mirrored.

If I leave off the floatingboxs the graph-rectangle show properly which makes sense as its the same code as the TextStamp version so the existence of the floating boxes is shifting the graph

This is how I generate the TextBox version

        var box = new FloatingBox((float)details.Width-padding, (float)details.Height-padding)
        {
            Border = new BorderInfo(BorderSide.All, details.AnnotationDefault.BorderColorARGB == 0 ? 0.0f : 1.0f, Color.Red),
              IsNeedRepeating = false,

            Left = (double)details.Left + padding - page.PageInfo.Margin.Left,
            Top = (double)details.Top + padding - page.PageInfo.Margin.Top,

            IsInLineParagraph = false,
            IsInNewPage = false,
            HorizontalAlignment = HorizontalAlignment.Left,
            VerticalAlignment = VerticalAlignment.Top,

            Margin = new MarginInfo(0, 0, 0, 0),
            Padding = new MarginInfo(0, 0, 0, 0),

            PositioningMode = ParagraphPositioningMode.Absolute,

            ZIndex = details.Zorder ?? 0,
        };

        var text = new TextFragment(message);

        text.TextState.ForegroundColor = GetColorFromARGB((uint)details.AnnotationDefault.FontColorARGB);
        text.TextState.Font = FontRepository.FindFont(details.AnnotationDefault.FontName);
        text.TextState.FontSize = (float)details.AnnotationDefault.FontSize;

        box.Paragraphs.Add(text);
        page.Paragraphs.Add(box);

        AddRectangle(page, (double)details.Left, (double)details.Top, (double)details.Width, (double)details.Height
            , (uint)details.AnnotationDefault.BackgroundColorARGB
            , (uint)details.AnnotationDefault.BorderColorARGB
            , (details.Zorder ?? 0) - 1);


    private static void AddRectangle(Page page, double x, double y, double width, double height, uint fillColorARGB, uint borderColorARGB, int zIndex, int radius = 0)
    {
        //_log.Debug($"  Rectangle Top:{y:000} Left:{x:000} Width:{width:000} Height:{height:000} Background:{fillColorARGB:X}");
        // Create a Graph object with dimensions matching the specified rectangle
        var graph = new Aspose.Pdf.Drawing.Graph(width, height)
        {
            // Prevent the graph from repositioning automatically
            IsChangePosition = false,
            // Set the Left coordinate position for the Graph instance
            Left = x - page.PageInfo.Margin.Left,
            // Set the Top coordinate position for the Graph instance
            Top = y - page.PageInfo.Margin.Top,
        };

        // Create a Rectangle object inside the Graph
        var rect = new Aspose.Pdf.Drawing.Rectangle(0, 0, (float)width, (float)height)
        {
            // Set the fill color of the rectangle
            GraphInfo =
            {
                FillColor = GetColorFromARGB(fillColorARGB),
                //LineWidth = borderColorARGB == 0 ? 0 : 1,
                //Color = GetColorFromARGB(borderColorARGB),
                //Color = borderColorARGB == 0 ? Color.Transparent : GetColorFromARGB(borderColorARGB),
                Color = Color.Red
            }
            ,
            RoundedCornerRadius = radius
        };

        // Add the rectangle to the Shapes collection of the Graph
        graph.Shapes.Add(rect);

        // Set the Z-Index for the Graph object to control layering
        graph.ZIndex = zIndex;

        // Add the Graph object to the Paragraphs collection of the page
        page.Paragraphs.Add(graph);
    }

I’m attaching a sample project. It not exact mainly because it starts with a new document instead of an existing one but the output is pretty much the same except that in this case the rectangle and the FloatingBox move together. In my real application they are opposite.

AnnotationExample.zip (2.2 KB)

@srmorley

We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): PDFNET-60717

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.