Adding shadow effect on iText elements - java

I have some problem with iText in Java. I need to produce a cell or rectangle with shadow just like in this example:
4 and 60 are in some kind of cell or rectangle with shadow. I don't know how to do it. Any help ?

The easiest way probably is to use a Chunk with a generic tag and a PdfPageEvent. This way you'll get an event callback when the Chunk is positioned on the page. The callback will give you the coordinates (rectangle) of the Chunk, allowing you to paint a border and a shadow at the correct location.
An example of such an event handler to paint a black border and a gray shadow:
class ShadowEvent extends PdfPageEventHelper {
#Override
public void onGenericTag(PdfWriter writer, Document document,
Rectangle rect, String text) {
PdfContentByte canvas = writer.getDirectContent();
// Paddings for the border
int paddingHorizontal = 20;
int paddingVertical = 5;
// Width of the shadow
int shadowwidth = 5;
// Calculate border location and size
float left = rect.getLeft() - paddingHorizontal;
float bottom = rect.getBottom() - paddingVertical;
float width = rect.getWidth() + 2*paddingHorizontal;
float height = rect.getHeight() + 2*paddingVertical;
canvas.saveState();
canvas.setColorFill(BaseColor.GRAY);
// Draw the shadow at the bottom
canvas.rectangle(left + shadowwidth, bottom - shadowwidth, width, shadowwidth);
canvas.fill();
// Draw the shadow at the right
canvas.rectangle(left + width, bottom - shadowwidth, shadowwidth, height);
canvas.fill();
canvas.setColorStroke(BaseColor.BLACK);
// Draw the border
canvas.rectangle(left, bottom, width, height);
canvas.stroke();
canvas.restoreState();
}
}
This shows how to use the generic tag:
Document doc = new Document();
PdfWriter pdfWriter = PdfWriter.getInstance(doc, outfile);
pdfWriter.setPageEvent(new ShadowEvent());
doc.open();
Chunk c = new Chunk("60");
c.setGenericTag("shadow");
doc.add(c);
doc.close();
(Note that the text parameter of the onGenericTag method will contain the String that was set to the Chunk with setGenericTag. This is "shadow" in the example above. It allows to differentiate between different tags. Since we're just using 1 tag here, I'm not using the text parameter.)
The result of the example looks like this:

Related

How to apply round corner border to table (single page / multipage)?

I want to apply round corner border to a table. This table is dynamic. That means it can grow up to multiple pages or can accommodate in single page.
If table comes in single page, then outermost corner of all four corner cells should be drawn as rounded.
If table grows up to multiple pages (say 3 pages), then outermost corner of all four corner cells should be drawn as rounded for all 3 pages.
Here is the approach which I am using to implement the above scenario.
public void createPdf(String dest) throws FileNotFoundException {
PdfWriter writer = new PdfWriter(DEST);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4, false);
Table table = new Table(3);
for (int i=0; i < 100; i++) {
for (int j=0; j < 3; j++) {
table.addCell(new Cell().add(new Paragraph("Cell content")));
}
}
table.setNextRenderer(new TableBorderRenderer(table));
document.add(table);
document.close();
}
TableBorderRenderer.java
public class TableBorderRenderer extends TableRenderer {
public TableBorderRenderer(Table modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new TableBorderRenderer((Table) modelElement);
}
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(0.5f).setStrokeColor(new DeviceRgb(255,0,0)).roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
}
Now the code is working fine as it is supposed to be, but there is an issue with the rendering. when i draw the white border on top of table border it does not overlap it completely. Also the outer red border is drawn slightly outside the expected area. In simple words, white rectangle and red rectangle are not coinciding with each other. So there is some gap coming between the outer border and cell borders.
I am attaching the generated output from above code. To notice the issue you might need to zoom the PDF a little bit.
I have some doubts regarding the same:
I am using a top canvas to get the expected solution. But is there any approach in which I can modify the table border directly while rendering? I tried
drawContext.getCanvas()
.saveState()
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
But in this approach border is overlapped by the cell borders (cell borders are needed as well). If I am missing something to prevent this issue, guide me.
Thanks.
The getOccupiedAreaBBox method gives you the outer bounding box of the border area. Borders, however, have thickness on their own, and when you draw lines in PDF by default you should pass the center of the line to the drawing operation, while you are passing the outer bbox coordinates, hence the small margin and imprecise overlap.
To fix the issue, you need to add half of the border line width to all of the edges of your rectangle:
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
All in all, the following customized code:
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,0,0))
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
Yields the following visual result:

PDFBox - obtain bounding box of rotated image

I'm working with PDFBox and trying to rotate an image and have it position correctly on the screen. The design editor I'm using outputs the following information about images that may be useful.
Image bounding box top-left coords (I'm using the bottom left coords to better suit PDFBox coord space.)
Image rotation in degrees
Image width & height
The translation appears to be off.
// Rotation
AffineTransform rotation = new AffineTransform();
rotation.rotate(Math.toRadians(360 - element.getAngle()),
element.getLeft() + scaledWidth/2,
adjustedYPos + scaledHeight/2);
stream.transform(new Matrix(rotation));
// Position & scale
AffineTransform mat = new AffineTransform(scaledWidth,
0,
0,
scaledHeight,
element.getLeft(),
adjustedYPos);
// Draw the final image
stream.drawImage(pdfImage, new Matrix(mat));
Rotations are based on the center of the image as an anchor point.
You can correctly position images using code like this:
void placeImage(PDDocument document, PDPage page, PDImageXObject image, float bbLowerLeftX, float bbLowerLeftY, float width, float height, float angle) throws IOException {
try ( PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true, true) ) {
float bbWidth = (float)(Math.abs(Math.sin(angle))*height + Math.abs(Math.cos(angle))*width);
float bbHeight = (float)(Math.abs(Math.sin(angle))*width + Math.abs(Math.cos(angle))*height);
contentStream.transform(Matrix.getTranslateInstance((bbLowerLeftX + .5f*bbWidth), (bbLowerLeftY + .5f*bbHeight)));
contentStream.transform(Matrix.getRotateInstance(angle, 0, 0));
contentStream.drawImage(image, -.5f*width, -.5f*height, width, height);
}
}
(PlaceRotatedImage utility method)
This method accepts coordinates as they are meaningful in the context of PDF, i.e. coordinate values and dimensions according to the default user space coordinate system of the given page (y values increasing upwards, the origin arbitrary but fairly fairly often in the lower left), (bounding) box given by lower left corner, angles as in math in counterclockwise radians...
If you need the parameters differently, you can fairly easily adapt the method, though. If you e.g. get the upper left corner of the bounding box instead of the lower left, you can simply subtract the bounding box height determined in the method as bbHeight to calculate the lower left y coordinate used here.
You can use this method like this:
PDPage page = ...;
PDRectangle mediaBox = page.getMediaBox();
float bbLowerLeftX = 50;
float bbLowerLeftY = 100;
try ( PDPageContentStream contentStream = new PDPageContentStream(document, page) ) {
contentStream.moveTo(bbLowerLeftX, mediaBox.getLowerLeftY());
contentStream.lineTo(bbLowerLeftX, mediaBox.getUpperRightY());
contentStream.moveTo(mediaBox.getLowerLeftX(), bbLowerLeftY);
contentStream.lineTo(mediaBox.getUpperRightX(), bbLowerLeftY);
contentStream.stroke();
}
PDImageXObject image = PDImageXObject.createFromByteArray(document, IOUtils.toByteArray(resource), "Image");
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, image.getWidth(), image.getHeight(), (float)(Math.PI/4));
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, .5f*image.getWidth(), .5f*image.getHeight(), 0);
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, .25f*image.getWidth(), .25f*image.getHeight(), (float)(9*Math.PI/8));
(PlaceRotatedImage test testPlaceByBoundingBox)
This code draws the left and bottom lines corresponding to the left and bottom side of the given lower left bounding box coordinates and draws an image at different magnifications and angles with the constant given lower left bounding box corner.
The result looks like this:
You can find more information on the calculation of the bounding box sizes in these answers:
Calculate Bounding box coordinates from a rotated rectangle
How to get width and height of the bounding box of a rotated rectangle
How to get size of a rotated rectangle
Find the Bounding Rectangle of Rotated Rectangle
...

Adding text top-right of PDF using ColumnText works for Portrait, not for Landscape

I am working on a project where as part of statements I need to attach arbitrary PDF files. These PDF files need to be marked by a title and page numbering, in the top-right corner of the PDF file. This is a legal requirement as these attachments are referred to by their title and total number of pages from the statements.
I (naively) hacked together some code that appears to be working on PDF files with pages in the Portrait orientation (at least the PDF files I tested with). However when I use this code on pages in a Landscape orientation, the title and numbering isn't visible.
The code:
PdfContentByte canvas = pdfStamper.getOverContent( pageNr );
Phrase phrase = new Phrase( sb.toString( ), new Font( FontFamily.HELVETICA, 9f ) ); // sb holds title + page numbering
float width = ColumnText.getWidth( phrase );
ColumnText.showTextAligned ( // draw text top-right
canvas,
Element.ALIGN_LEFT,
phrase,
canvas.getPdfDocument( ).right( ) - width, //x
canvas.getPdfDocument( ).top( ) + 9, //y
0 //rotation
);
Examples:
Portrait where it appears to work:
Landscape where it doesn't work:
Questions:
Where did I go wrong?
Is it possible to write such a piece of code that does it right for all possible page orientations?
If so, how?
You are adding the content, but you are adding it at the wrong place. See PageSize of PDF always the same between landscape and portrait with itextpdf
Let's assume that you are working with an A4 page using portrait orientation. That pages measures 595 by 842 user units. 595 is the width; 842 is the height.
Now let's switch to landscape. This can be done in two different ways:
define a width of 595 and a height of 842, and a rotation of 90 degrees.
define a width of 842 and a height of 595.
Which way is used to define the landscape orientation will have an impact on the value of the right() and top() method. I am pretty sure that you are adding the header to the landscape pages, but you are adding them outside the visible area of the page.
For those interested, I ended up doing it as follows. This works for both Portrait and Landscape orientations. This uses the PdfReader.getPageSizeWithRotation method to get the proper page size.
private String pageText(int pageNr, int pageTotal) {
return ""; // generate string to display top-right of PDF here
}
private void addDocumentObjects(int pageNr, PdfReader pdfReader, PdfStamper pdfStamper) {
final float pageMargin = 25f;
final float textSize = 9f;
final float lineMargin = 5f;
Phrase phrase = new Phrase (
pageText(pageNr, pdfReader.getNumberOfPages()),
new Font(FontFamily.HELVETICA, textSize)
);
final float phraseWidth = ColumnText.getWidth(phrase);
PdfContentByte canvas = pdfStamper.getOverContent(pageNr);
com.itextpdf.text.Rectangle pageRectangle = pdfReader.getPageSizeWithRotation(pageNr);
// draw white background rectangle before adding text + line
canvas.setColorFill(BaseColor.WHITE);
canvas.rectangle (
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin), //y
phraseWidth, // width
textSize + lineMargin //height
);
canvas.fill();
// draw text top right
canvas.setColorFill(BaseColor.BLACK);
ColumnText.showTextAligned (
canvas, //canvas
Element.ALIGN_LEFT, //alignment
phrase, //phrase
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin), //y
0 //rotation
);
// draw line under text
canvas.setColorStroke(BaseColor.BLACK);
canvas.setLineWidth(1);
canvas.moveTo (
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin) - lineMargin //y
);
canvas.lineTo (
pageRectangle.getRight(pageMargin), //x
pageRectangle.getTop(pageMargin) - lineMargin //y
);
canvas.stroke();
}

how to do a word wrap on a short string with TextLayout in java Graphics2d?

How would you effectively do a word wrap on a short label String below with TextLayout in java?
My label are only two or three words long
Some examples:
1. Inflatable Greenhouse D10 ;
2. Command and Control Center A5;
3. Jason Boris;
I'd like to wrap the words in such a way to shape as like a square as possible, rather than one long rectangle.
So my question is: What does it take to wrap the building names to the 2nd line, instead of one long line? See pic below:
Is there a way to set the maximum number of characters to be contained in a line of text and wrap the remaining characters to the second line and so on (it would need to account for whitespace)?
For example, I'd like to wrap the name "Residential Quarter D12" into three lines.
Residential
Quarter
D12
and wrap "Command and Control D16" into four lines.
Command
and
Control
D16
Wouldn't it be nice if TextLayout can understand html codes like a regular JLabel!? Then it'll make things easy:
String label = "<html>" + "Inflatable" + "<br>" + "Greenhouse" + "<br>" + "D10" + "</html>";
Note: it doesn't have to be one word per line. But I'd like to have them "centered" on each line
What I have was the following method for generating a BufferedImage of the building name labels o or just the first and last name of a person.
private BufferedImage createLabelImage(
String label, Font font, FontRenderContext fontRenderContext, Color labelColor,
Color labelOutlineColor) {
// Determine bounds.
TextLayout textLayout1 = new TextLayout(label, font, fontRenderContext);
Rectangle2D bounds1 = textLayout1.getBounds();
// Get label shape.
Shape labelShape = textLayout1.getOutline(null);
// Create buffered image for label.
int width = (int) (bounds1.getWidth() + bounds1.getX()) + 4;
int height = (int) (bounds1.getHeight()) + 4;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// Get graphics context from buffered image.
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate(2D - bounds1.getX(), 2D - bounds1.getY());
// Draw label outline.
Stroke saveStroke = g2d.getStroke();
g2d.setColor(labelOutlineColor);
g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(labelShape);
g2d.setStroke(saveStroke);
// Fill label
g2d.setColor(labelColor);
g2d.fill(labelShape);
// Dispose of image graphics context.
g2d.dispose();
return bufferedImage;
}
As you can see, this method can only create a BufferedImage form of label with one line of text only.
As I overlay these BufferedImage labels on a map, they look too long and they overlap one another.
That's why I need to make each label to shape like a square as possible.
Let me try to suggest an algoritm.
Split the label by space to get list of words and measure each word to get array
int[] wordWidths;
int minWidth=max(wordWidths);
int height=the row height const;
int minHeight=height;
int maxHeight=wordWidths.length*height;
int currentWidth=minWidht;
int currentHeight=maxHeight;
while(currentWidth<currentHeight || wordWidths.length>1) {
int mergedWidth=find minimal sum of neighbour words' widths
replace the 2 widths with the mergedWidth reducing the wordWidthssize
currentHeight=wordWidths.length*height;
}
Or you can try to rely on components. I would define a JTextArea instance assigning the label there and trying to play with the wrap reducing width 1 by 1 and measuring preferred height for the width.
When optimal size is achived you can call theWrappedJtextArea.printAll(g) to paint it on your BufferedImage's Graphics.

Correct text position center in rectangle iText

I try to draw text inside rectangle which fit rectangle size, like my previous question, I want text align center in rectangle.
The problem is display text has wrong Y coordinate, look like this one:
And here is my code:
PdfContentByte cb = writer.getDirectContent();
Rectangle rect = new Rectangle(100, 150, 100 + 120, 150 + 50);
cb.saveState();
ColumnText ct = new ColumnText(writer.getDirectContent());
Font font = new Font(BaseFont.createFont());
float maxFontSize;
// try to get max font size that fit in rectangle
font.setSize(maxFontSize);
ct.setText(new Phrase("test", font));
ct.setSimpleColumn(rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop());
ct.go();
// draw the rect
cb.setColorStroke(BaseColor.BLUE);
cb.rectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight());
cb.stroke();
cb.restoreState();
I even draw text like this:
cb.saveState();
cb.beginText();
cb.moveText(rect.getLeft(), rect.getBottom());
cb.setFontAndSize(BaseFont.createFont(), maxSize);
cb.showText("test");
cb.endText();
cb.setColorStroke(BaseColor.BLUE);
cb.rectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight());
cb.stroke();
And got the result:
So I wonder how can itext render text base on the coordinates? Because I use the same rectangle frame for text and rectangle bound.
I'm not sure if I understand your question correctly. I'm assuming you want to fit some text into a rectangle vertically, but I don't understand how you calculate the font size, and I don't see you setting the leading anywhere (which you can avoid by using ColumnText.showAligned()).
I've created an example named FitTextInRectangle which results in the PDF chunk_in_rectangle.pdf. Due to rounding factors (we're working with float values), the word test slightly exceeds the rectangle, but the code shows how to calculate a font size that makes the text fit more or less inside the rectangle.
In your code samples, the baseline is defined by the leading when using ColumnText (and the leading is wrong) or the bottom coordinate of the rectangle when using showText() (and you forgot to take into account value of the descender).

Categories

Resources