Box Cloud Annotation Appearance in iText - annotations

This is my sample code to draw a box cloud annotation. I used code in PDFBox's implementation to draw a box cloud but i have a little problem when used in iText. I modified the border class and some parts to be usable in iText.
you can find the border class here.
My problem is, the top and right border clouds are not drawn. it seems their location is drawn beyond the rect difference. I figure the issue is with drawing the curves in cloudyPolygonImpl(). maybe itext has different ways to draw in PdfAppearance? I am not sure.
This is the what i have so far.
public class Test {
public static void main(String[] args) throws Exception {
PdfReader reader = new PdfReader("src.pdf");
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("result.pdf"));
PdfDictionary be = new PdfDictionary();
be.put(PdfName.S, PdfName.C);
be.put(PdfName.I, new PdfNumber(1));
Rectangle location = new Rectangle(123.6f, 584.4f, 252.6f, 653.4f);
PdfAnnotation stamp = PdfAnnotation.createSquareCircle(stamper.getWriter(), location, "", true);
stamp.setBorderStyle(new PdfBorderDictionary(1, PdfBorderDictionary.STYLE_SOLID));
stamp.put(new PdfName("BE"), be);
stamp.setColor(BaseColor.RED);
PdfContentByte cb = stamper.getOverContent(1);
PdfAppearance app = cb.createAppearance(location.getWidth(), location.getHeight());
stamp.setAppearance(PdfName.N, app);
PdfArray stickyRect = stamp.getAsArray(PdfName.RECT);
Rectangle annotRect = new Rectangle(stickyRect.getAsNumber(0).floatValue(),
stickyRect.getAsNumber(1).floatValue(),
stickyRect.getAsNumber(2).floatValue(),
stickyRect.getAsNumber(3).floatValue());
PdfArray arrDiff = annotation.getAsArray(PdfName.RD);
Rectangle annotRectDiff = null;
if (arrDiff != null) {
annotRectDiff = new Rectangle(arrDiff.getAsNumber(0).floatValue(), arrDiff.getAsNumber(1).floatValue(),
arrDiff.getAsNumber(2).floatValue(), arrDiff.getAsNumber(3).floatValue()
}
// Create cloud appearance
CBorder cborder = new CBorder(app, 1, 1, annotRect);
cborder.createCloudyRectangle(annotRectDiff);
stamp.put(PdfName.RECT, new PdfRectangle(cborder.getRectangle()));
stamp.put(PdfName.RD, new PdfArray(new float[] {
cborder.getRectDifference().getLeft(),
cborder.getRectDifference().getBottom(),
cborder.getRectDifference().getRight(),
cborder.getRectDifference().getTop() }));
app.rectangle(cborder.getBBox());
app.transform(cborder.getMatrix());
app.setColorStroke(BaseColor.RED);
app.setLineWidth(1);
app.stroke();
stamper.addAnnotation(stamp, 1);
stamper.close();
reader.close();
}
}
The correct output should be that all borders be drawn with cloud but currently only the left and bottom are drawn.

(This answer is based on the code in revision 3 of your question as the changes in revision 4 introduced multiple errors.)
Your code here creates an invalid annotation appearance stream:
CBorder cborder = new CBorder(app, 1, 1, annotRect);
cborder.createCloudyRectangle(null);
stamp.put(PdfName.RECT, new PdfRectangle(cborder.getRectangle()));
stamp.put(PdfName.RD, new PdfArray(new float[] {
cborder.getRectDifference().getLeft(),
cborder.getRectDifference().getBottom(),
cborder.getRectDifference().getRight(),
cborder.getRectDifference().getTop() }));
app.rectangle(cborder.getBBox());
app.transform(cborder.getMatrix());
app.setColorStroke(BaseColor.RED);
app.setLineWidth(1);
app.stroke();
Its upper part creates a path:
2 j
121.58 588.63 m
122.06 588.95 122.6 589.18 123.16 589.3 c
120.73 588.78 119.18 586.4 119.7 583.96 c
120.19 581.67 122.35 580.14 124.68 580.44 c
...
122.06 596.42 122.6 596.64 123.16 596.76 c
121.09 596.32 119.6 594.49 119.6 592.36 c
119.6 590.87 120.34 589.47 121.58 588.63 c
h
Then app.rectangle(cborder.getBBox()) does not create anything (beware, this rectangle overload does not what you expect it to do!).
Then app.transform(cborder.getMatrix()) adds a change to the current transformation matrix, app.setColorStroke(BaseColor.RED) adds a change of the stroking color, and app.setLineWidth(1) adds a change of the line width:
1 0 0 1 -118.68 -579.48 cm
1 0 0 RG
1 w
And finally app.stroke() adds the command to stroke the path:
S
But between the definition of a path and the corresponding path drawing command, only clipping path instructions are allowed! Cf. Figure 9 – Graphics Objects – in the PDF specification ISO 32000-1.
You can fix this code like this, pulling up color and line width changes, and directly using the cloud bounding box:
// Create cloud appearance
app.setColorStroke(BaseColor.RED);
app.setLineWidth(1);
CBorder cborder = new CBorder(app, 1, 1, annotRect);
cborder.createCloudyRectangle(null);
stamp.put(PdfName.RECT, new PdfRectangle(cborder.getRectangle()));
stamp.put(PdfName.RD, new PdfArray(new float[] {
cborder.getRectDifference().getLeft(),
cborder.getRectDifference().getBottom(),
cborder.getRectDifference().getRight(),
cborder.getRectDifference().getTop() }));
app.stroke();
app.setBoundingBox(cborder.getBBox());
(CloudBoxAnnotation test testDrawLikeChitgoksImproved)
This in particular changes the result (as seen in Adobe Acrobat) from
to

Related

iTextSharp IExtRenderListener and boundingbox [duplicate]

I have a pdf which comprises of some data, followed by some whitespace. I don't know how large the data is, but I'd like to trim off the whitespace following the data
PdfReader reader = new PdfReader(PDFLOCATION);
Rectangle rect = new Rectangle(700, 2000);
Document document = new Document(rect);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(SAVELCATION));
document.open();
int n = reader.getNumberOfPages();
PdfImportedPage page;
for (int i = 1; i <= n; i++) {
document.newPage();
page = writer.getImportedPage(reader, i);
Image instance = Image.getInstance(page);
document.add(instance);
}
document.close();
Is there a way to clip/trim the whitespace for each page in the new document?
This PDF contains vector graphics.
I'm usung iTextPDF, but can switch to any Java library (mavenized, Apache license preferred)
As no actual solution has been posted, here some pointers from the accompanying itext-questions mailing list thread:
As you want to merely trim pages, this is not a case of PdfWriter + getImportedPage usage but instead of PdfStamper usage. Your main code using a PdfStamper might look like this:
PdfReader reader = new PdfReader(resourceStream);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("target/test-outputs/test-trimmed-stamper.pdf"));
// Go through all pages
int n = reader.getNumberOfPages();
for (int i = 1; i <= n; i++)
{
Rectangle pageSize = reader.getPageSize(i);
Rectangle rect = getOutputPageSize(pageSize, reader, i);
PdfDictionary page = reader.getPageN(i);
page.put(PdfName.CROPBOX, new PdfArray(new float[]{rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()}));
stamper.markUsed(page);
}
stamper.close();
As you see I also added another argument to your getOutputPageSize method to-be. It is the page number. The amount of white space to trim might differ on different pages after all.
If the source document did not contain vector graphics, you could simply use the iText parser package classes. There even already is a TextMarginFinder based on them. In this case the getOutputPageSize method (with the additional page parameter) could look like this:
private Rectangle getOutputPageSize(Rectangle pageSize, PdfReader reader, int page) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle result = new Rectangle(finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
System.out.printf("Text/bitmap boundary: %f,%f to %f, %f\n", finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
return result;
}
Using this method with your file test.pdf results in:
As you see the code trims according to text (and bitmap image) content on the page.
To find the bounding box respecting vector graphics, too, you essentially have to do the same but you have to extend the parser framework used here to inform its listeners (the TextMarginFinder essentially is a listener to drawing events sent from the parser framework) about vector graphics operations, too. This is non-trivial, especially if you don't know PDF syntax by heart yet.
If your PDFs to trim are not too generic but can be forced to include some text or bitmap graphics in relevant positions, though, you could use the sample code above (probably with minor changes) anyways.
E.g. if your PDFs always start with text on top and end with text at the bottom, you could change getOutputPageSize to create the result rectangle like this:
Rectangle result = new Rectangle(pageSize.getLeft(), finder.getLly(), pageSize.getRight(), finder.getUry());
This only trims top and bottom empty space:
Depending on your input data pool and requirements this might suffice.
Or you can use some other heuristics depending on your knowledge on the input data. If you know something about the positioning of text (e.g. the heading to always be centered and some other text to always start at the left), you can easily extend the TextMarginFinder to take advantage of this knowledge.
Recent (April 2015, iText 5.5.6-SNAPSHOT) improvements
The current development version, 5.5.6-SNAPSHOT, extends the parser package to also include vector graphics parsing. This allows for an extension of iText's original TextMarginFinder class implementing the new ExtRenderListener methods like this:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
List<Vector> points = new ArrayList<Vector>();
if (renderInfo.getOperation() == PathConstructionRenderInfo.RECT)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
points.add(new Vector(x, y, 1));
points.add(new Vector(x+w, y, 1));
points.add(new Vector(x, y+h, 1));
points.add(new Vector(x+w, y+h, 1));
}
else if (renderInfo.getSegmentData() != null)
{
for (int i = 0; i < renderInfo.getSegmentData().size()-1; i+=2)
{
points.add(new Vector(renderInfo.getSegmentData().get(i), renderInfo.getSegmentData().get(i+1), 1));
}
}
for (Vector point: points)
{
point = point.cross(renderInfo.getCtm());
Rectangle2D.Float pointRectangle = new Rectangle2D.Float(point.get(Vector.I1), point.get(Vector.I2), 0, 0);
if (currentPathRectangle == null)
currentPathRectangle = pointRectangle;
else
currentPathRectangle.add(pointRectangle);
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (textRectangle == null)
textRectangle = currentPathRectangle;
else
textRectangle.add(currentPathRectangle);
}
currentPathRectangle = null;
return null;
}
#Override
public void clipPath(int rule)
{
}
(Full source: MarginFinder.java)
Using this class to trim the white space results in
which is pretty much what one would hope for.
Beware: The implementation above is far from optimal. It is not even correct as it includes all curve control points which is too much. Furthermore it ignores stuff like line width or wedge types. It actually merely is a proof-of-concept.
All test code is in TestTrimPdfPage.java.

adding a textbox to the right corner of the existing pdf using ITextSharp in C#

I tiied to add a TextBox to the right corner of the existing pdf using c#, but im unable to get it done. I have wrote the following code,but it is not helping in solving the problem, can any body please suggest me
using (MemoryStream stream = new MemoryStream())
{
PdfReader reader = new PdfReader(bytes);
PdfReader.unethicalreading = true;
Paragraph p = new Paragraph();
Document doc = new Document();
using (PdfStamper stamper = new PdfStamper(reader, stream))
{
PdfContentByte canvas = stamper.GetOverContent(1);
iTextSharp.text.Rectangle size = reader.GetPageSizeWithRotation(1);
//PdfContentByte cb = null;
//PdfImportedPage page;
int pages = reader.NumberOfPages;
for (int i = 1; i <= pages; i++)
{
var size1 = reader.GetPageSize(i);
w = size1.Width;
h = size1.Height;
stamper.FormFlattening = true;
TextField tf = new TextField(stamper.Writer, new iTextSharp.text.Rectangle(0, 0, 300, 100), displaytext);
//Change the orientation of the text
tf.Rotation = 0;
stamper.AddAnnotation(tf.GetTextField(), i);
}
}
bytes = stream.ToArray();
}
File.WriteAllBytes(str, bytes);
As the OP clarified in comments to the question, he wants
to add the text as a page content in the right bottom corner of the page and
the page content previously existing there to be removed.
A simple implementation of this would include
first covering the existing page content with a filled rectangle and
then writing text there.
These tasks can be achieved with these helper methods:
void EmptyTextBoxSimple(PdfStamper stamper, int pageNumber, Rectangle boxArea, BaseColor fillColor)
{
PdfContentByte canvas = stamper.GetOverContent(pageNumber);
canvas.SaveState();
canvas.SetColorFill(fillColor);
canvas.Rectangle(boxArea.Left, boxArea.Bottom, boxArea.Width, boxArea.Height);
canvas.Fill();
canvas.RestoreState();
}
and
ColumnText GenerateTextBox(PdfStamper stamper, int pageNumber, Rectangle boxArea)
{
PdfContentByte canvas = stamper.GetOverContent(pageNumber);
ColumnText columnText = new ColumnText(canvas);
columnText.SetSimpleColumn(boxArea);
return columnText;
}
E.g. like this:
using (PdfReader reader = new PdfReader(source))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create)))
{
Rectangle cropBox = reader.GetCropBox(1);
Rectangle bottomRight = new Rectangle(cropBox.GetRight(216), cropBox.Bottom, cropBox.Right, cropBox.GetBottom(146));
EmptyTextBoxSimple(stamper, 1, bottomRight, BaseColor.WHITE);
ColumnText columnText = GenerateTextBox(stamper, 1, bottomRight);
columnText.AddText(new Phrase("Some test text to draw into a text box in the lower right corner of the first page"));
columnText.Go();
}
For this source page
the sample code generates this
Addendum
In a comment the OP indicated
it is working for all files but for some pdf files it is displaying in the middle
Eventually he supplied a sample file for which the issue occurs. And indeed, with this file the issue could be reproduced.
The cause for the issue is that the pages in the sample file use page rotation, something that iText (only) partially allows users to ignore. In particular iText automatically rotates text to be upright after rotation and transforms coordinates, but when retrieving the cropbox of a page, one still has to apply rotation before making use of it coordinates. Thus, a more complete example would be like this:
using (PdfReader reader = new PdfReader(source))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create)))
{
Rectangle cropBox = reader.GetCropBox(1);
int rotation = reader.GetPageRotation(1);
while (rotation > 0)
{
cropBox = cropBox.Rotate();
rotation -= 90;
}
Rectangle bottomRight = new Rectangle(cropBox.GetRight(216), cropBox.Bottom, cropBox.Right, cropBox.GetBottom(146));
EmptyTextBoxSimple(stamper, 1, bottomRight, BaseColor.WHITE);
ColumnText columnText = GenerateTextBox(stamper, 1, bottomRight);
columnText.AddText(new Phrase("Some test text to draw into a text box in the lower right corner of the first page"));
columnText.Go();
}

How to decide the right coordinates in the middle of a PDF page?

I am new to iText and I looked at its many examples. The thing I have hard time to figure it out is the rectangle. On the page
http://developers.itextpdf.com/examples/form-examples-itext5/multiline-fields
there are many examples with hard-coded values for Rectangle objects. For example:
Rectangle rect = new Rectangle(36, 770, 144, 806);
My problem is that I create one Paragraph and I would like to add a fillable text input box (multi-lines) beneath it. How do I know the exact values for creating a Rectangle object that can be nicely put just after the paragraph. The size of the text of a Paragraph can change. So I cannot assume any hard-coded value.
In iText 5, iText keeps track of the coordinates of the content when using document.add(). You could take control yourself by adding content at absolute positions (e.g. by using ColumnText), but that's hard, because then you have to keep track of many things yourself (for instance: you have to introduce page breaks yourself when the content reaches the bottom of the page).
If you leave the control of the coordinates to iText, you can get access to these coordinates by using page events.
Take a look at the example below, where we keep track of the start and the end of a Paragraph in the onParagraph() and onParagraphEnd() method. This code sample is not easy to understand, but it's the only way to get the coordinates of a Paragraph in iText 5 for instance if we want to draw a rectangle around a block of text. As you can read at the bottom of that page, iText 7 makes it much easier to meet this requirement.
If you stick to iText 5, it's much easier to use generic tags to define locations. See the GenericFields example, where we use empty Chunks that result in fields. If you want to see a screen shot of the result, see Add PdfPCell to Paragraph
In your case, I'd create a Paragraph containing a Chunk that spans different lines, and I'd add the field in the onGenericTag() method of a page event.
Suppose that we have the following text file: jekyll_hyde.txt
How do we convert it to a PDF that looks like this:
Note the blue border that is added to the titles, and the page number at the bottom of each page. In iText 5, these elements are added using page events:
class MyPageEvents extends PdfPageEventHelper {
protected float startpos = -1;
protected boolean title = true;
public void setTitle(boolean title) {
this.title = title;
}
#Override
public void onEndPage(PdfWriter writer, Document document) {
Rectangle pagesize = document.getPageSize();
ColumnText.showTextAligned(
writer.getDirectContent(),
Element.ALIGN_CENTER,
new Phrase(String.valueOf(writer.getPageNumber())),
(pagesize.getLeft() + pagesize.getRight()) / 2,
pagesize.getBottom() + 15,
0);
if (startpos != -1)
onParagraphEnd(writer, document,
pagesize.getBottom(document.bottomMargin()));
startpos = pagesize.getTop(document.topMargin());
}
#Override
public void onParagraph(PdfWriter writer, Document document,
float paragraphPosition) {
startpos = paragraphPosition;
}
#Override
public void onParagraphEnd(PdfWriter writer, Document document,
float paragraphPosition) {
if (!title) return;
PdfContentByte canvas = writer.getDirectContentUnder();
Rectangle pagesize = document.getPageSize();
canvas.saveState();
canvas.setColorStroke(BaseColor.BLUE);
canvas.rectangle(
pagesize.getLeft(document.leftMargin()),
paragraphPosition - 3,
pagesize.getWidth() - document.leftMargin() - document.rightMargin(),
startpos - paragraphPosition);
canvas.stroke();
canvas.restoreState();
}
}
We can use the following code to convert a text file to a PDF and introduce the page event to the PdfWriter:
public void createPdf(String dest)
throws DocumentException, IOException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
MyPageEvents events = new MyPageEvents();
writer.setPageEvent(events);
document.open();
BufferedReader br = new BufferedReader(new FileReader(TEXT));
String line;
Paragraph p;
Font normal = new Font(FontFamily.TIMES_ROMAN, 12);
Font bold = new Font(FontFamily.TIMES_ROMAN, 12, Font.BOLD);
boolean title = true;
while ((line = br.readLine()) != null) {
p = new Paragraph(line, title ? bold : normal);
p.setAlignment(Element.ALIGN_JUSTIFIED);
events.setTitle(title);
document.add(p);
title = line.isEmpty();
}
document.close();
}
Source: developers.itextpdf.com

itextpdf Redaction :Partly redacted text string is fully removed

Used itextpdf-5.5.9 and itext-xtra-5.5.9
I am trying to applying redaction on Partly text string but after applied redaction whole string is removed from document.Please find attached screenshot.
PdfReader reader = new PdfReader(src);
PdfCleanUpProcessor cleaner= null;
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(targetPdf));
stamper.setRotateContents(false);
List<PdfCleanUpLocation> cleanUpLocations = new ArrayList<PdfCleanUpLocation>();
Rectangle rectangle = new Rectangle(380, 640, 430, 665);
cleanUpLocations.add(new PdfCleanUpLocation(1, rectangle, BaseColor.BLACK));
cleaner = new PdfCleanUpProcessor(cleanUpLocations, stamper);
cleaner.cleanUp();
stamper.close();
reader.close();
The OP clarified in comments that it is indeed his expectation that redaction only removes text which is completely contained in the redaction area; text, though, whose bounding box is even partially outside that area, is expected to remain.
This expectation may be unwise as far as security of redaction is concerned because this way text a casual redactor does not see anymore due to the colored redaction area may still remain in the PDF content available to text extraction or even simple copy&paste.
If in spite of such reservations one still wants to tweak PdfCleanup to work like expected by the OP, one essentially merely has to change the PdfCleanUpRegionFilter used by the PdfCleanUpProcessor: The filter implementation used by default rejects a glyph (and so marks it for removal) if its bounding box intersects the redaction area. To fulfill the OP's expectations, this behavior has to be replaced by a check whether the bounding box is completely contained in the redaction area.
This sounds simple. Unfortunately it is not as simple as it sounds because the clean-up code is not designed for easy replacement of the region filter implementation, many relevant objects or methods are private or at best package protected.
Thus, to achieve the OP's desired behavior I simply copied all of the classes from the com.itextpdf.text.pdf package into an own package, therein added a new filter class derived from my copy of PdfCleanUpRegionFilter with the different text rejection algorithm mentioned above, and then changed the copy of PdfCleanUpProcessor to use this other filter class:
/**
* In contrast to the base class {#link PdfCleanUpRegionFilter}, this filter
* only rejects text <b>completely</b> inside the redaction zone. The original
* also rejects text located merely <b>partially</b> inside the redaction zone.
*/
public class StrictPdfCleanUpRegionFilter extends PdfCleanUpRegionFilter
{
public StrictPdfCleanUpRegionFilter(List<Rectangle> rectangles)
{
super(rectangles);
this.rectangles = rectangles;
}
/**
* Checks if the text is COMPLETELY inside render filter region.
*/
#Override
public boolean allowText(TextRenderInfo renderInfo) {
LineSegment ascent = renderInfo.getAscentLine();
LineSegment descent = renderInfo.getDescentLine();
Point2D[] glyphRect = new Point2D[] {
new Point2D.Float(ascent.getStartPoint().get(0), ascent.getStartPoint().get(1)),
new Point2D.Float(ascent.getEndPoint().get(0), ascent.getEndPoint().get(1)),
new Point2D.Float(descent.getEndPoint().get(0), descent.getEndPoint().get(1)),
new Point2D.Float(descent.getStartPoint().get(0), descent.getStartPoint().get(1)),
};
for (Rectangle rectangle : rectangles)
{
boolean glyphInRectangle = true;
for (Point2D point2d : glyphRect)
{
glyphInRectangle &= rectangle.getLeft() <= point2d.getX();
glyphInRectangle &= point2d.getX() <= rectangle.getRight();
glyphInRectangle &= rectangle.getBottom() <= point2d.getY();
glyphInRectangle &= point2d.getY() <= rectangle.getTop();
}
if (glyphInRectangle)
return false;
}
return true;
}
List<Rectangle> rectangles;
}
(StrictPdfCleanUpRegionFilter)
public class StrictPdfCleanUpProcessor {
...
private PdfCleanUpRegionFilter createFilter(List<PdfCleanUpLocation> cleanUpLocations) {
List<Rectangle> regions = new ArrayList<Rectangle>(cleanUpLocations.size());
for (PdfCleanUpLocation location : cleanUpLocations) {
regions.add(location.getRegion());
}
return new StrictPdfCleanUpRegionFilter(regions);
}
...
}
(StrictPdfCleanUpProcessor, my copy of PdfCleanUpProcessor)
All classes can be found here.
It can be used just like the original cleanup implementation, one merely has to remember using the copied classes, not the original ones:
try ( InputStream resource = getClass().getResourceAsStream("Document.pdf");
OutputStream result = new FileOutputStream(new File(OUTPUTDIR, "Document-redacted-strict.pdf")) )
{
PdfReader reader = new PdfReader(resource);
StrictPdfCleanUpProcessor cleaner= null;
PdfStamper stamper = new PdfStamper(reader, result);
stamper.setRotateContents(false);
List<mkl.testarea.itext5.pdfcleanup.PdfCleanUpLocation> cleanUpLocations = new ArrayList<>();
Rectangle rectangle = new Rectangle(380, 640, 430, 665);
cleanUpLocations.add(new mkl.testarea.itext5.pdfcleanup.PdfCleanUpLocation(1, rectangle, BaseColor.BLACK));
cleaner = new StrictPdfCleanUpProcessor(cleanUpLocations, stamper);
cleaner.cleanUp();
stamper.close();
reader.close();
}
(RedactText test method testRedactStrictForMayankPandey)
The example PDF provided by the OP
After redaction using the original classes
After redaction using the tweaked classes
A sanity check with the tweaked classes
To be sure the tweaked classes still removed any text at all, I enlarged the redaction area so that "heet", the last characters of "Document Submission Sheet", were completely contained in the redaction area:
try ( InputStream resource = getClass().getResourceAsStream("Document.pdf");
OutputStream result = new FileOutputStream(new File(OUTPUTDIR, "Document-redacted-strict-large.pdf")) )
{
PdfReader reader = new PdfReader(resource);
StrictPdfCleanUpProcessor cleaner= null;
PdfStamper stamper = new PdfStamper(reader, result);
stamper.setRotateContents(false);
List<mkl.testarea.itext5.pdfcleanup.PdfCleanUpLocation> cleanUpLocations = new ArrayList<>();
Rectangle rectangle = new Rectangle(380, 640, 430, 680);
cleanUpLocations.add(new mkl.testarea.itext5.pdfcleanup.PdfCleanUpLocation(1, rectangle, BaseColor.BLACK));
cleaner = new StrictPdfCleanUpProcessor(cleanUpLocations, stamper);
cleaner.cleanUp();
stamper.close();
reader.close();
}
(RedactText test method testRedactStrictForMayankPandeyLarge)
And indeed, copy&paste (and other text extraction methods, too) now only render
"Document Submission S".

Using iText7, how do I fill an otherwise empty column with a rectangle?

Using iText7 I wish to fill an otherwise empty column with a bordered rectangle headed by some text. The border methods seem to have disappeared from Rectangle in iText7 and the only examples I can find use them. If Rectangle is the correct approach how do I do this? If not, what is the correct approach?
Please take a look at Chapter 2 of the tutorial "iText 7: Building Blocks"
In this tutorial, we create a Rectangle object and we draw it to a PdfCanvas object:
Rectangle rectangle = new Rectangle(36, 650, 100, 100);
pdfCanvas.rectangle(rectangle);
pdfCanvas.stroke();
How to get a PdfCanvas object?
Either you create it from a PdfPage object you've created yourself:
OutputStream fos = new FileOutputStream(dest);
PdfWriter writer = new PdfWriter(fos);
PdfDocument pdf = new PdfDocument(writer);
PdfPage page = pdf.addNewPage();
PdfCanvas pdfCanvas = new PdfCanvas(page);
Or you get an existing page from the PdfDocument object:
PdfCanvas canvas = new PdfCanvas(pdf, pdf.getNumberOfPages());
You can tweak the line width, dash pattern, line color,... using different methods in the PdfCanvas object.
There are other ways to draw a rectangle, but in one of your previous questions, you mentioned a ColumnDocumentRenderer. If your current question is part of the same context, you already have Rectangle objects and if you have a ColumnDocumentRenderer, you have access to a PdfCanvas object. You could easily automate your app to make it draw a rectangle around every column that is rendered.
Of course: since you never accepted my previous answer, my assumption could be wrong.
For better or worse this seemed to achieve my objective:
AreaBreak nextArea = new AreaBreak(AreaBreakType.NEXT_AREA);
document.add(nextArea);
float h = document.getRenderer().getCurrentArea().getBBox().getHeight();
float w = document.getRenderer().getCurrentArea().getBBox().getWidth();
Paragraph endB = new Paragraph(" ");
endB.setHeight(h);
endB.setWidth(w);
SolidBorder b = new SolidBorder(2);
endB.setBorder(b);
document.add(endB);