Java PDF creation using iTEXT - itext

How to create a paragraph with background color in iText using java.
I tried with Chunk but its highlighted colour for text upto its length and there no bg color applied between lines.

Your task to create a paragraph with background color (in particular uninterrupted between lines) can be implemented by means of a page event listener which locally stores paragraph start positions and draws background rectangles as soon as the end of paragraph is signaled:
public class ParagraphBackground extends PdfPageEventHelper
{
public BaseColor color = BaseColor.YELLOW;
public void setColor(BaseColor color)
{
this.color = color;
}
public boolean active = false;
public void setActive(boolean active)
{
this.active = active;
}
public float offset = 5;
public float startPosition;
#Override
public void onStartPage(PdfWriter writer, Document document)
{
startPosition = document.top();
}
#Override
public void onParagraph(PdfWriter writer, Document document, float paragraphPosition)
{
this.startPosition = paragraphPosition;
}
#Override
public void onEndPage(PdfWriter writer, Document document)
{
if (active)
{
PdfContentByte cb = writer.getDirectContentUnder();
cb.saveState();
cb.setColorFill(color);
cb.rectangle(document.left(), document.bottom() - offset,
document.right() - document.left(), startPosition - document.bottom());
cb.fill();
cb.restoreState();
}
}
#Override
public void onParagraphEnd(PdfWriter writer, Document document, float paragraphPosition)
{
if (active)
{
PdfContentByte cb = writer.getDirectContentUnder();
cb.saveState();
cb.setColorFill(color);
cb.rectangle(document.left(), paragraphPosition - offset,
document.right() - document.left(), startPosition - paragraphPosition);
cb.fill();
cb.restoreState();
}
}
}
(ParagraphBackground.java)
Whenever this page event listener is set active at an end of a paragraph, the paragraph background is colored.
It can be used like this:
#Test
public void testParagraphBackgroundEventListener() throws DocumentException, FileNotFoundException
{
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("document-with-paragraph-backgrounds.pdf"));
ParagraphBackground back = new ParagraphBackground();
writer.setPageEvent(back);
document.open();
document.add(new Paragraph("Hello,"));
document.add(new Paragraph("In this document, we'll add several paragraphs that will trigger page events. As long as the event isn't activated, nothing special happens, but let's make the event active and see what happens:"));
back.setActive(true);
document.add(new Paragraph("This paragraph now has a background. Isn't that fantastic? By changing the event, we can even draw a border, change the line width of the border and many other things. Now let's deactivate the event."));
back.setActive(false);
document.add(new Paragraph("This paragraph no longer has a background."));
document.close();
}
(ColorParagraphBackground.java)
The result looks like this:
Credits
This actually is a rip-off of Bruno Lowagie's answer to "How to add border to paragraph in itext pdf library in java?" with small changes to fill instead of stroke background rectangles. He even had already back then though about an application like this as his sample program wrote:
This paragraph now has a border. Isn't that fantastic? By changing the event, we can even provide a background color

Related

In iText 7 java how do you update Link text after it's already been added to the document

I am using iText7 to build a table of contents for my document. I know all the section names before I start, but don't know what the page numbers will be. My current process is to create a table on the first page and create all the Link objects with generic text "GO!". Then as I add sections I add through the link objects and update the text with the page numbers that I figured out as I created the document.
However, at the end, what gets written out for the link is "GO!", not the updated page number values I set as I was creating the rest of the document.
I did set the immediateFlush flag to false when I created the Document.
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
List<Link>links = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]>notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
links = new ArrayList<>();
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
links.add(link);
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document)
throws Exception {
int count = 0;
for (String[] note : notes) {
int numberOfPages = pdfDocument.getNumberOfPages();
Link link = links.get(count);
link.setText(""+(numberOfPages+1));
Paragraph noteText = new Paragraph(note[2]);
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
}
Quite more work needs to be done compared to the code you have now because the changes to the elements don't take any effect once you've added them to the document. Immediate flush set to false allows you to relayout the elements, but that does not happen automatically. The way you calculate the current page the paragraph will be placed on (int numberOfPages = pdfDocument.getNumberOfPages();) is not bulletproof because in some cases pages might be added in advance, even if the content is not going to be placed on them immediately.
There is a very low level way to achieve your goal but with the recent version of iText (7.1.15) there is a simpler way as well, which still requires some work though. Basically your use case is very similar to target-counter concept in CSS, with page counter being the target one in your case. To support target counters in pdfHTML add-on we added new capabilities to our layout module which are possible to use directly as well.
To start off, we are going to tie our Link elements to the corresponding Paragraph elements that they will point to. We are going to do it with ID property in layout:
link.setProperty(Property.ID, String.valueOf(count));
noteText.setProperty(Property.ID, String.valueOf(count));
Next up, we are going to create custom renderers for our Link elements and Paragraph elements. Those custom renderers will interact with TargetCounterHandler which is the new capability in layout module I mentioned in the introduction. The idea is that during layout operation the paragraph will remember the page on which it was placed and then the corresponding link element (remember, link elements are connected to paragraph elements) will ask TargetCounterHandler during layout process of that link element which page the corresponding paragraph was planed on. So in a way, TargetCounterHandler is a connector.
Code for custom renderers:
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
Don't forget to assign the custom renderers to their elements:
link.setNextRenderer(new CustomLinkRenderer(link));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
Now, the other thing we need to do it relayout. You already set immediateFlush to false and this is needed for relayout to work. Relayout is needed because on the first layout loop we will not know all the positions of the paragraphs, but we will already have placed the links on the pages by the time we know those positions. So we need the second pass to use the information about page numbers the paragraphs will reside on and set that information to the links.
Relayout is pretty straightforward - once you've put all the content you just need to call a single dedicated method:
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
One caveat is that for now you also need to subclass the DocumentRenderer since there is an additional operation that needs to be done that is not performed under the hood - propagation of the target counter handler to the root renderer we will be using for the second layout operation:
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
document.setRenderer(new CustomDocumentRenderer(document, false));
And now we are done. Here is our visual result:
Complete code looks as follows:
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]> notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
document.setRenderer(new CustomDocumentRenderer(document, false));
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
link.setProperty(Property.ID, String.valueOf(count));
link.setNextRenderer(new CustomLinkRenderer(link));
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document) {
int count = 0;
for (String[] note : notes) {
Paragraph noteText = new Paragraph(note[2]);
noteText.setProperty(Property.ID, String.valueOf(count));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
}

Is there a way to wrap text in a tree viewer (Eclipse) (Not a simple Tree)

viewer.getControl().addListener(SWT.MeasureItem, new Listener() {
#Override
public void handleEvent(Event event) {
TreeItem item = (TreeItem)event.item;
String text = getText(item, event.index);
Point size = event.gc.textExtent(text);
event.width = size.x;
event.height = Math.max(event.height, size.y);
}
});
In the above code snippet the listener is added but it is not coming to handleEvent method at all.
For TreeViewer do not try to add Listeners as that will interfere with the operation of the viewer.
To draw the lines yourself use a Label Provider which extends OwnerDrawLabelProvider and implement the measure, erase and paint methods.

JavaFX-8 Canvas within BorderPane

I'm pretty new to this whole JavaFX thing, as you can probably tell. This is an issue I ran into recently while playing around with the Canvas node. I've got a BorderPane as my root node, and I'd like to have a Canvas occupy all available space within the center of it. However, I'm having some trouble implementing that exact behavior. Here's the just of what I've been trying:
public void start(Stage stage){
BorderPane root=new BorderPane();
Canvas canvas=new Canvas(250,250);
//canvas.widthProperty().bind(root.widthProperty());
//canvas.heightProperty().bind(root.heightProperty());
GraphicsContext gc=canvas.getGraphicsContext2D();
new AnimationTimer(){
#Override
public void handle(long l){
double width=canvas.getWidth(),height=canvas.getHeight();
gc.clearRect(0,0,width,height);
gc.strokeRect(0,0,width,height);
gc.strokeLine(0,0,width,height);
gc.strokeLine(0,height,width,0);
}
}.start();
root.setCenter(canvas);
root.setBottom(new Button("Placeholder"));
root.setTop(new Button("Placeholder"));
stage.setScene(new Scene(root));
stage.show();
}
Instead of expanding as the Pane does, my Canvas just stays centered within it, retaining its original size. If the two commented lines near the top are re-added, the Canvas grows and shrinks as the Pane does, but without regarding the dimensions of its center (as expected). Is there a way to apply this sort of binding behavior to just the center of the BorderPane, or perhaps another way to do this entirely that I'm unaware of?
EDIT: Just found a much nicer solution today (19.05.2014):
http://fxexperience.com/2014/05/resizable-grid-using-canvas/
So much easier and shorter than mine -.-
Now my original approach:
I had the same problem as you do.
I found a really ugly workaround you can use, but maybe there is another method doing this...
My Workaround:
class MyCanvas {
private Canvas cv;
private StackPane box;
public MyCanvas(Stage stg) {
cv = new Canvas(500, 500);
box = new StackPane();
box.getChildren().add(cv);
//When the Stage size changes, the canvas size becomes resetted
//but the Hbox expandes automatically to fit the with again
//and so the canvas become resized to fit the HBox
stg.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
resize();
}
});
stg.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
resize();
}
});
box.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
adapt();
}
});
box.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
adapt();
}
});
paint();
}
private void paint(){
//Paint something ....
GraphicsContext ctx = cv.getGraphicsContext2D();
double w = cv.getWidth();
double h = cv.getHeight();
ctx.clearRect(0, 0, w, h);
ctx.setFill(Color.BLUE);
ctx.fillRect(0, 0, w, h);
}
//Add this HBox to your content pane
public StackPane getBox() {
return box;
}
public void resize(){
cv.setWidth(50);
cv.setHeight(50);
}
private void adapt(){
cv.setHeight(box.getHeight());
cv.setWidth(box.getWidth());
paint();
}
}
And in your Main:
public class Root extends Application{
public static void main(String[] args){ launch(args); }
public void start(Stage stg){
MyCanvas cv = new MyCanvas(stg);
BorderPane pane = new BorderPane();
pane.setCenter(pane.getBox());
stg.setScene(new Scene(pane));
stg.show();
}
}
Hope this helps you solving your problem.
nZeloT

displaying jpg to a jPanel

So i am trying to add an image to my program after a button is pressed with a certain value entered into a text field. I want the image to be blank until the button is pressed with the correct value. The only issue i seem to be having is making the photo appear, i know the output should be working. here is the code i have for adding the label.
photo = new JLabel();
photo.setBounds(425, 170, 400, 360);
contentPane.add(photo);
ImageIcon icon = new ImageIcon("AwsomeSauce.jpg");
here is the code for when the value is entered correctly
if (error) {
photo.setIcon(image);
}
now im still pretty new at this so go easy on me.
Here is simple example for you:
public class Frame extends JFrame {
private JLabel label;
public Frame() {
getContentPane().add(label = new JLabel(),BorderLayout.SOUTH);
JButton b = new JButton("show Icon");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
URL resource = getClass().getResource("/res/3_disc.png");
ImageIcon ico = new ImageIcon(resource);
label.setIcon(ico);
}
});
getContentPane().add(b,BorderLayout.NORTH);
}
public static void main(String[] args) {
Frame frame = new Frame();
frame.setTitle("This is a test");
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
"/res/3_disc.png" - is my icon which is placed in my project in res folder.

Hyperlink at footer using itextSharp

I need to put a hyperlink at the footer of my PDF generated using iTextSharp.
I know how to use PdfPageEventHelper to print some text in the footer but not putting a hyperlink.
public class PdfHandlerEvents: PdfPageEventHelper
{
private PdfContentByte _cb;
private BaseFont _bf;
public override void OnOpenDocument(PdfWriter writer, Document document)
{
_cb = writer.DirectContent;
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
_bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
Rectangle pageSize = document.PageSize;
_cb.SetRGBColorFill(100, 100, 100);
_cb.BeginText();
_cb.SetFontAndSize(_bf, 10);
_cb.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "More information", pageSize.GetRight(200), pageSize.GetBottom(30), 0);
_cb.EndText();
}
}
How do I make the text "More information" a hyperlink?
Edited:
After the answer from Chris below, I have also figure out how to print image at the footer, here is the code:
Image pic = Image.GetInstance(#"C:\someimage.jpg");
pic.SetAbsolutePosition(0, 0);
pic.ScalePercent(25);
PdfTemplate tpl = _cb.CreateTemplate(pic.Width, pic.Height);
tpl.AddImage(pic);
_cb.AddTemplate(tpl, 0, 0);
The Document object generally lets you work with abstract things like Paragraph and Chunk but in doing so you lose absolute positioning. The PdfWriter and PdfContentByte objects give you absolute positioning but you need to work with lower level objects like raw text.
Luckily there is a happy middle-ground object called ColumnText that should do what you're looking for. You can think of the ColumnText as basically a table and most people use it as a single column table so you can actually just think of it as a rectangle that you add objects to. See the comments in the code below for any questions.
public class PdfHandlerEvents : PdfPageEventHelper {
private PdfContentByte _cb;
private BaseFont _bf;
public override void OnOpenDocument(PdfWriter writer, Document document) {
_cb = writer.DirectContent;
}
public override void OnEndPage(PdfWriter writer, Document document) {
base.OnEndPage(writer, document);
_bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
iTextSharp.text.Rectangle pageSize = document.PageSize;
//Create our ColumnText bound to the canvas
var ct = new ColumnText(_cb);
//Set the dimensions of our "box"
ct.SetSimpleColumn(pageSize.GetRight(200), pageSize.GetBottom(30), pageSize.Right, pageSize.Bottom);
//Create a new chunk with our text and font
var c = new Chunk("More Information", new iTextSharp.text.Font(_bf, 10));
//Set the chunk's action to a remote URL
c.SetAction(new PdfAction("http://www.aol.com"));
//Add the chunk to the ColumnText
ct.AddElement(c);
//Tell the ColumnText to draw itself
ct.Go();
}
}