Skip to content

PDFPageBuilder.ResetColor() voids a TransformationMatrix Operations if added after #1245

@SinisterMaya

Description

@SinisterMaya

Hi, First of all, I want to thank you for this awesome project. I have encountered a surprising behavior with the page operation list:

My setup is Windows 11 25H2 (26200.7623) Developping in .NET 8

I work on a project where I need to add some more data to PDF for production. Since my PDFs are Landscaped, I need to rotate them, append data at the desired location, then rotate back. Here is the code I am working with.

using (PdfDocument document = PdfDocument.Open(@"PathToFile"))
{
    var page = document.GetPage(1);
    string dest = @"OutputName";
    if (System.IO.File.Exists(dest))
    {
        File.Delete(dest);
    }
    using (FileStream fileStream = new FileStream(dest, FileMode.CreateNew, FileAccess.ReadWrite))
    using (PdfDocumentBuilder builder = new PdfDocumentBuilder(fileStream))
    {
        PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica);

        PdfPageBuilder pageBuilder = builder.AddPage(document, page.Number);

        pageBuilder.NewContentStreamBefore();
        TransformationMatrix inversse = new TransformationMatrix();
        if (page.Rotation.Value == 90)
        {
            TransformationMatrix rotationMatrix = TransformationMatrix.GetRotationMatrix(90);
            TransformationMatrix translationMatrix = TransformationMatrix.GetTranslationMatrix(0, -page.Height);
            TransformationMatrix matrix = translationMatrix.Multiply(rotationMatrix);
            inversse = matrix.Inverse();
            pageBuilder.CurrentStream.Operations.Add(new ModifyCurrentTransformationMatrix(new double[] { matrix.A, matrix.B, matrix.C, matrix.D, matrix.E, matrix.F }));
        }

        pageBuilder.SetStrokeColor(100, 100, 100);
        pageBuilder.SetTextAndFillColor(100, 100, 100);
        pageBuilder.SetTextRenderingMode(TextRenderingMode.Fill);

        double scalingFactor = page.Width / (22 * 72);
        PdfPoint textOrigin = new PdfPoint(0, 72 * 1.5 * scalingFactor);

        var projectLetters = pageBuilder.MeasureText("16254", 24 * scalingFactor, textOrigin, font);
        var textWidth = projectLetters.Max(x => x.GlyphRectangle.Right) - projectLetters.Min(x => x.GlyphRectangle.Left);
        PdfPoint projectTextOrigin = textOrigin.Translate((page.Width / 2) - textWidth / 2, 0);
        projectLetters = pageBuilder.MeasureText("16254", 24 * scalingFactor, projectTextOrigin, font);

        var qtyLetters = pageBuilder.MeasureText($"Qte: {24}", 24 * scalingFactor, textOrigin, font);
        var qtyWidth = qtyLetters.Max(x => x.GlyphRectangle.Right) - qtyLetters.Min(x => x.GlyphRectangle.Left);
        PdfPoint qtyTextOrigin = textOrigin.Translate((page.Width / 2) - qtyWidth / 2, -(36 * scalingFactor));
        qtyLetters = pageBuilder.MeasureText($"Qte: {24}", 24 * scalingFactor, qtyTextOrigin, font);

        var bottomOfText = qtyLetters.Min(x => x.GlyphRectangle.Bottom);
        var topOfText = projectLetters.Max(x => x.GlyphRectangle.Top);
        var leftOfText = double.Min(projectLetters.Min(x => x.GlyphRectangle.Left), qtyLetters.Min(x => x.GlyphRectangle.Left));
        var rightOfText = double.Max(projectLetters.Max(x => x.GlyphRectangle.Right), qtyLetters.Max(x => x.GlyphRectangle.Right));

        //make the rectangle .125in away from the text
        var rectangleOffset = (.125 * 72) * scalingFactor;
        bottomOfText = bottomOfText - rectangleOffset;
        topOfText = topOfText + rectangleOffset;
        leftOfText = leftOfText - rectangleOffset;
        rightOfText = rightOfText + rectangleOffset;

        var boxWidth = rightOfText - leftOfText;
        var boxHeight = topOfText - bottomOfText;
        double lineWidth = 3 * scalingFactor;

        PdfPoint rectangleOrigin = new PdfPoint(leftOfText, bottomOfText);

        PdfPoint outerRectangleOrigin = rectangleOrigin.Translate(-rectangleOffset, -rectangleOffset);
        double outerRectangleWidth = boxWidth + rectangleOffset * 2;
        double outerRectangleHeight = boxHeight + rectangleOffset * 2;

        var words = page.GetWords();
        var docstrum = new DocstrumBoundingBoxes(new DocstrumBoundingBoxes.DocstrumBoundingBoxesOptions()
        {
            WithinLineBounds = new DocstrumBoundingBoxes.AngleBounds(-30, 30),
            BetweenLineBounds = new DocstrumBoundingBoxes.AngleBounds(45, 135),
            BetweenLineMultiplier = 0.1
        });
        PdfRectangle roi = new PdfRectangle(outerRectangleOrigin, new PdfPoint(outerRectangleOrigin.X + outerRectangleWidth, outerRectangleOrigin.Y + outerRectangleHeight));

        var blocks = docstrum.GetBlocks(words).Where(x => x.BoundingBox.IntersectsWith(roi)).Select(x => x.BoundingBox).ToList();

        if (blocks.Count > 0)
        {
            double[] pushRight = new double[blocks.Count];
            double[] pushLeft = new double[blocks.Count];
            double[] pushUp = new double[blocks.Count];
            double[] pushDown = new double[blocks.Count];

            //Border on C format starts at 0.5 inch from the bottom and 0.75 from the sides
            //TitleBlock on C format is 8.824 inches wide, and 2.367 high
            PdfPoint cartoucheBottomLeft = new((22.0 - 0.75 - 8.824) * 72.0 * scalingFactor, 0.5 * 72 * scalingFactor);
            PdfPoint cartoucheTopRight = new((22.0 - 0.75) * 72 * scalingFactor, (0.5 + 2.367) * 72 * scalingFactor);
            PdfRectangle cartouche = new PdfRectangle(cartoucheBottomLeft, cartoucheTopRight);

            double rightLimit = roi.Bottom < cartouche.Top ? cartouche.Left : cartouche.Right;
            double leftLimit = 0.75 * 72 * scalingFactor;
            double bottomLimit = 0.5 * 72 * scalingFactor;
            double topLimit = page.Height - (0.5 * 72 * scalingFactor);


            for (int i = 0; i < blocks.Count; i++)
            {
                pushRight[i] = blocks[i].Right - roi.Left;
                pushLeft[i] = roi.Right - blocks[i].Left;
                pushUp[i] = blocks[i].Top - roi.Bottom;
                pushDown[i] = roi.Top - blocks[i].Bottom;
            }

            double smallestDistance = double.MaxValue;
            PdfPoint movementVector = new PdfPoint();

            var minRight = pushRight.Min();
            if (roi.Right + minRight <= rightLimit)
            {
                smallestDistance = minRight;
                movementVector = new PdfPoint(minRight, 0);
            }

            var minLeft = pushLeft.Min();
            if (roi.Left - minLeft >= leftLimit && minLeft < smallestDistance)
            {
                smallestDistance = minLeft;
                movementVector = new PdfPoint(-minLeft, 0);
            }
            var minUp = pushUp.Min();
            if (roi.Top + minUp <= topLimit && minUp < smallestDistance)
            {
                smallestDistance = minUp;
                movementVector = new PdfPoint(0, minUp);
            }
            var minDown = pushDown.Min();
            if (roi.Bottom - minDown >= bottomLimit && minDown < smallestDistance)
            {
                smallestDistance = minDown;
                movementVector = new PdfPoint(0, -minDown);
            }

            rectangleOrigin = rectangleOrigin.Translate(movementVector.X, movementVector.Y);
            outerRectangleOrigin = outerRectangleOrigin.Translate(movementVector.X, movementVector.Y);
            qtyTextOrigin = qtyTextOrigin.Translate(movementVector.X, movementVector.Y);
            projectTextOrigin = projectTextOrigin.Translate(movementVector.X, movementVector.Y);
        }

        pageBuilder.DrawRectangle(rectangleOrigin, boxWidth, boxHeight, lineWidth);
        //add another rectangle
        pageBuilder.DrawRectangle(outerRectangleOrigin, outerRectangleWidth, outerRectangleHeight, lineWidth);
        var drawnQtyLetters = pageBuilder.AddText($"Qte: {24}", 24 * scalingFactor, qtyTextOrigin, font);
        var drawnProjectLetters = pageBuilder.AddText("16254", 24 * scalingFactor, projectTextOrigin, font);
        //pageBuilder.ResetColor(); //WILL NOT CAUSE PROBLEM
        pageBuilder.CurrentStream.Operations.Add(new ModifyCurrentTransformationMatrix(new double[] { inversse.A, inversse.B, inversse.C, inversse.D, inversse.E, inversse.F }));
        pageBuilder.ResetColor(); //THIS CAUSE BEHAVIOR ISSUE
        builder.Build();
    }
}

As commented, If I call pageBuilder.ResetColor() before the last rotation operation, everything works as intended. If I place ResetColor() after the rotation, the last rotation is ignored?

Here is the resulting PDF if I place the ResetColor() after :

Image

And this is when I place it before :

Image

I have also attached the original PDF.

16457_03-20-529-004-A.pdf

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugdocument-editingRelated to creating or editing/modifying documents

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions