Following the given examples pdfClown can both highlight a specific text and draw a rectangle around the respective words.
Is there a possibility to make this reactangle editable afterwards with the Adobe Acrobat?
My current workflow (as planned):
Import a document
Search document for Highlightings
Determine the Hightlighting's color
Draw a rectangle around the outer boundaries of the rectangle
add a callout to a another rectangle containing a letter, depending on the determined color
I can not (e.g.) drag the rectangle around the formerly highlighted word with Acrobat Reader, as far as I can see. I used the provided example from pdfClown's webpage to draw a reactangle around every character.
Is there something that I forgot to consider?
File inFile = null;
String inFilePath = "/path/to/inputFile/input_highlight.pdf";
String outDirPath = "/tmp";
try {
inFile = new File(inFilePath);
} catch (Exception e) {
throw new RuntimeException(inFilePath + " file access error.", e);
}
Document document = inFile.getDocument();
Pages pages = document.getPages();
PageStamper stamper = new PageStamper();
for (Page page : pages) {
stamper.setPage(page);
PageAnnotations annotations = page.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.getColor() == null) {
continue;
}
Rectangle2D textStringBox = annotation.getBox();
PrimitiveComposer composer = stamper.getBackground();
composer.setStrokeColor(DeviceRGBColor.Black);
textStringBox.setRect(annotation.getBox().getX(), annotation.getBox().getY(), annotation.getBox().getWidth(), annotation.getBox().getHeight());
composer.drawRectangle(textStringBox);
composer.stroke();
composer.beginLocalState();
composer.setStrokeColor(DeviceRGBColor.Black);
composer.end();
stamper.flush();
System.out.println("Text: " + annotation.getText());
System.out.println("Color: " + annotation.getColor());
System.out.println("Coordinates: " + annotation.getBox().toString());
annotation.setColor(DeviceRGBColor.White);
}
}
As it seems your main issue is that
I can not (e.g.) drag the rectangle around the formerly highlighted word with Acrobat Reader
The reason is that you draw your rectangle in the page content (the PageStamper you use is documented as Tool for content insertion into existing pages). The page content is fixed, in particular as far as Acrobat Reader is concerned; Acrobat Reader only allows you to move annotations.
If you want to have a rectangle you can drag around, therefore, you have to use a rectangle annotation. Rectangle annotations can be created like this:
new org.pdfclown.documents.interaction.annotations.Rectangle(
page,
new Rectangle(50, 370, 100, 30),
"Text of the Rectangle annotation"
).withColor(DeviceRGBColor.get(Color.RED))
.withBorder(new Border(1, new LineDash(new double[]{5})))
.withAuthor("Stefano")
.withSubject("Rectangle")
.withPopup(new Popup(
page,
new Rectangle2D.Double(200, 325, 200, 75),
"Text of the Popup annotation (this text won't be visible as associating popups to markup annotations overrides the former's properties with the latter's)"
));
(AnnotationSample.java)
You also mention that you want to add a callout; a callout annotation can be created like this:
new StaticNote(
page,
new Rectangle(250, 90, 150, 70),
"Text of the Callout note annotation"
).withLine(
new StaticNote.CalloutLine(
page,
new Point(250,125),
new Point(150,125),
new Point(100,100)
)
)
.withLineEndStyle(LineEndStyleEnum.OpenArrow)
.withBorder(new Border(1))
.withColor(DeviceRGBColor.get(Color.YELLOW));
(AnnotationSample.java)
Related
I need to create PDF using some java API which has the capabilities like HTML. Basically, I want to create a form which can take input from users, perform some basic client side validations and also generate this PDF programmatically using Java. I am also looking for rich HTML like features like expand, collapse, hyperlinks, add a section on a button click etc. So basically I am trying to create an HTML like page but within PDF.
I have tried using itext but able to only to do only handful of things and not able to add dynamism into PDF. Is there any tool/API which supports this?
PDF itself allows you to embed (a subset of) javascript.
This embedded code can be linked to document events (e.g. opening the document) or specific form elements (e.g. clicking a button, changing the text in a text-input field).
This is a page from their website entitled 'Making a PDF interactive' which focusses on adding form elements.
The book (iText in Action) by Bruno Lowagie (original founder of iText) also goes in great detail. It even shows how to program a calculator in a PDF document, page 232.
I'm just going to copy-paste the relevant section here.
Listing 7.29 Calculator
public void addTextField(PdfWriter writer, Rectangle rect, String name) {
PdfFormField field = PdfFormField.createTextField(writer, false, false, 0);
field.setFieldName(name);
field.setWidget(rect, PdfAnnotation.HIGHLIGHT_NONE);
field.setQuadding(PdfFormField.Q_RIGHT);
field.setFieldFlags(PdfFormField.FF_READ_ONLY);
writer.addAnnotation(field);
}
public void addPushButton(PdfWriter writer, Rectangle rect, String btn, String script) {
float w = rect.getWidth();
float h = rect.getHeight();
PdfFormField pushbutton = PdfFormField.createPushButton(writer);
pushbutton.setFieldName("btn_" + btn);
pushbutton.setWidget(rect, PdfAnnotation.HIGHLIGHT_PUSH);
PdfContentByte cb = writer.getDirectContent();
pushbutton.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, createAppearance(cb, btn, BaseColor.GRAY, w, h));
pushbutton.setAppearance(PdfAnnotation.APPEARANCE_ROLLOVER, createAppearance(cb, btn, BaseColor.RED, w, h));
pushbutton.setAppearance(PdfAnnotation.APPEARANCE_DOWN, createAppearance(cb, btn, BaseColor.BLUE, w, h));
pushbutton.setAdditionalActions(PdfName.U, PdfAction.javaScript(script, writer));
pushbutton.setAdditionalActions(PdfName.E, PdfAction.javaScript( "this.showMove('" + btn + "');", writer));
pushbutton.setAdditionalActions(PdfName.X, PdfAction.javaScript( "this.showMove(' ');", writer));
writer.addAnnotation(pushbutton);
}
public PdfAppearance createAppearance(PdfContentByte cb, String btn, BaseColor color, float w, float h) {
PdfAppearance app = cb.createAppearance(w, h);
app.setColorFill(color);
app.rectangle(2, 2, w - 4, h - 4);
app.fill();
app.beginText();
app.setColorFill(BaseColor.BLACK);
app.setFontAndSize(bf, h / 2);
app.showTextAligned(Element.ALIGN_CENTER, btn, w / 2, h / 4, 0);
app.endText();
return app;
}
I am using Apache PDFBox(version : 2.0.8) to generate a PDF document from my .jspx page.
In my .jspx page form, there are many fields, so i decided to arrange all the fields as 2 column layout. So, I need suggestion to achieve the layout using PDFbox.
// Sample Code snippet I have used to generate PDF is as below.
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDFont font = getFontDef();
PDPageContentStream contentStream =new PDPageContentStream(document, page);
contentStream.beginText();
contentStream.showText("Name: " );
contentStream.setFont(getFontDef().COURIER ,15);
contentStream.showText ("Rajeev");
contentStream.showText("Address: " +"BNG");
contentStream.newLine();
contentStream.showText("State: " +"KAR");
contentStream.showText("Country: " +"INDIA");
contentStream.endText();
contentStream.close();
I need to show label(i.e. Name, Address,State and Country) in bold font and Value(i.e. Rajeev, BNG,KAR,IND respectively) as normal font.
To get bold font for the label, I have tried as shown below
contentStream.setFont(getFontDef().COURIER ,15);
and it is working , but I have to add the above line before each label field. is there any better approaches I can use to set bold font for all the labels?
I am also facing some issue in aligning these form fields into two column layout. Ex
Name: Rajeev Address: BNG
State: KAR Country: IND
i.e. Name and Address in first line and Name should be in first column and Address should be in second column. Similarly, State and country in second line and State in first column and country is in second column.
To arrange text in columns, you can use the newLineAtOffset to move horizontally, not or at least not only vertically.
To switch back and forth between bold and normal type faces, you usually do indeed have to set and reset the font again and again. Alternatively you could first draw all bold text and then all normal text (or the other way around), but then you'd have to use more newLineAtOffset calls to jump around. Furthermore, some PDF viewers might follow your jumps when you try to select and copy text.
PDFont fontNormal = PDType1Font.HELVETICA;
PDFont fontBold = PDType1Font.HELVETICA_BOLD;
PDPageContentStream contentStream =new PDPageContentStream(document, page);
contentStream.beginText();
contentStream.newLineAtOffset(100, 600);
contentStream.setFont(fontBold, 15);
contentStream.showText("Name: ");
contentStream.setFont(fontNormal, 15);
contentStream.showText ("Rajeev");
contentStream.newLineAtOffset(200, 00);
contentStream.setFont(fontBold, 15);
contentStream.showText("Address: " );
contentStream.setFont(fontNormal, 15);
contentStream.showText ("BNG");
contentStream.newLineAtOffset(-200, -20);
contentStream.setFont(fontBold, 15);
contentStream.showText("State: " );
contentStream.setFont(fontNormal, 15);
contentStream.showText ("KAR");
contentStream.newLineAtOffset(200, 00);
contentStream.setFont(fontBold, 15);
contentStream.showText("Country: " );
contentStream.setFont(fontNormal, 15);
contentStream.showText ("INDIA");
contentStream.endText();
contentStream.close();
(ArrangeText test testArrangeTextForUser2967784)
I don't know what your method getFontDef() returns, so I chose fixed fonts for bold and normal text here.
The result:
I've started using PDFClown some weeks ago. My purpose is multi-word highlighting, mainly on newspapers. Starting from the org.pdfclown.samples.cli.TextHighlightSample example, I succeeded in extracting multi-word positions and highlighting them. I even solved some problems due to text ordering and matching in most cases.
Unfortunately my framework includes FPDI and it does not consider PDFAnnotations. So, all the content outside of a page content stream, like text annotations and other so called markup annotations, get lost.
So any suggestion on creating "Text Highlighting" with PdfClown and without using PDF annotations?
To not have the highlight in an annotation but instead in the actual page content stream, one has to put the graphic commandos into the page content stream which in case of the org.pdfclown.samples.cli.TextHighlightSample example are implicitly put into the normal annotation appearance stream.
This can be implemented like this:
org.pdfclown.files.File file = new org.pdfclown.files.File(resource);
Pattern pattern = Pattern.compile("S", Pattern.CASE_INSENSITIVE);
TextExtractor textExtractor = new TextExtractor(true, true);
for (final Page page : file.getDocument().getPages())
{
final List<Quad> highlightQuads = new ArrayList<Quad>();
Map<Rectangle2D, List<ITextString>> textStrings = textExtractor.extract(page);
final Matcher matcher = pattern.matcher(TextExtractor.toString(textStrings));
textExtractor.filter(textStrings, new TextExtractor.IIntervalFilter()
{
#Override
public boolean hasNext()
{
return matcher.find();
}
#Override
public Interval<Integer> next()
{
return new Interval<Integer>(matcher.start(), matcher.end());
}
#Override
public void process(Interval<Integer> interval, ITextString match)
{
{
Rectangle2D textBox = null;
for (TextChar textChar : match.getTextChars())
{
Rectangle2D textCharBox = textChar.getBox();
if (textBox == null)
{
textBox = (Rectangle2D) textCharBox.clone();
}
else
{
if (textCharBox.getY() > textBox.getMaxY())
{
highlightQuads.add(Quad.get(textBox));
textBox = (Rectangle2D) textCharBox.clone();
}
else
{
textBox.add(textCharBox);
}
}
}
highlightQuads.add(Quad.get(textBox));
}
}
#Override
public void remove()
{
throw new UnsupportedOperationException();
}
});
// Highlight the text pattern match!
ExtGState defaultExtGState = new ExtGState(file.getDocument());
defaultExtGState.setAlphaShape(false);
defaultExtGState.setBlendMode(Arrays.asList(BlendModeEnum.Multiply));
PrimitiveComposer composer = new PrimitiveComposer(page);
composer.getScanner().moveEnd();
// TODO: reset graphics state here.
composer.applyState(defaultExtGState);
composer.setFillColor(new DeviceRGBColor(1, 1, 0));
{
for (Quad markupBox : highlightQuads)
{
Point2D[] points = markupBox.getPoints();
double markupBoxHeight = points[3].getY() - points[0].getY();
double markupBoxMargin = markupBoxHeight * .25;
composer.drawCurve(new Point2D.Double(points[3].getX(), points[3].getY()),
new Point2D.Double(points[0].getX(), points[0].getY()),
new Point2D.Double(points[3].getX() - markupBoxMargin, points[3].getY() - markupBoxMargin),
new Point2D.Double(points[0].getX() - markupBoxMargin, points[0].getY() + markupBoxMargin));
composer.drawLine(new Point2D.Double(points[1].getX(), points[1].getY()));
composer.drawCurve(new Point2D.Double(points[2].getX(), points[2].getY()),
new Point2D.Double(points[1].getX() + markupBoxMargin, points[1].getY() + markupBoxMargin),
new Point2D.Double(points[2].getX() + markupBoxMargin, points[2].getY() - markupBoxMargin));
composer.fill();
}
}
composer.flush();
}
file.save(new File(RESULT_FOLDER, "multiPage-highlight-content.pdf"), SerializationModeEnum.Incremental);
(HighlightInContent.java method testHighlightInContent)
You will recognize the text extraction frame from the original example. Merely now the quads from a whole page are collected before they are processed, and the processing code (which mostly has been borrowed from TextMarkup.refreshAppearance()) draws forms representing the quads into the page content.
Beware, to make this work generically, the graphics state has to be reset before inserting the new instructions (the position is marked with a TODO comment). This can be done either by applying save/restore state or by actually counteracting unwanted changed state entries. Unfortunately I did not see how to do the former in PDF Clown and have not yet had the time to do the latter.
I need to create an User guide, where I've to put the content in 2 different language but on the same page. so the first half of the page would be in English while the second part would be in French. (In future they might ask for 3rd language also, but maximum 3). So each page would have 2 blocks. How can I achieve this using iTextPDF in java ?
UPDATE
Following is the structure for more insight of the question.
If I understand your question correctly, you need to create something like this:
In this screen shot, you see the first part of the first book of Caesar's Commentaries on the Gallic War. Gallia omnia est divisa in partes tres, and so is each page in this document: the upper part shows the text in Latin, the middle part shows the text in English, the lower part shows the text in French. If you read the text, you'll discover that Belgians like me are considered being the bravest of all (although we aren't as civilized as one would wish). See three_parts.pdf if you want to take a look at the PDF.
This PDF was created with the ThreeParts example. In this example, I have 9 text files:
http://itextpdf.com/sites/default/files/liber1_1_la.txt
http://itextpdf.com/sites/default/files/liber1_1_en.txt
http://itextpdf.com/sites/default/files/liber1_1_fr.txt
http://itextpdf.com/sites/default/files/liber1_2_la.txt
http://itextpdf.com/sites/default/files/liber1_2_en.txt
http://itextpdf.com/sites/default/files/liber1_2_fr.txt
http://itextpdf.com/sites/default/files/liber1_3_la.txt
http://itextpdf.com/sites/default/files/liber1_3_en.txt
http://itextpdf.com/sites/default/files/liber1_3_fr.txt
Liber is the latin word for book, so all files are snippets from the first book, more specifically sections 1, 2, and 3, in Latin, English and French.
This is how I defined the languages and he rectangles for each language:
public static final String[] LANGUAGES = { "la", "en", "fr" };
public static final Rectangle[] RECTANGLES = {
new Rectangle(36, 581, 559, 806),
new Rectangle(36, 308.5f, 559, 533.5f),
new Rectangle(36, 36, 559, 261) };
In my code, I loop over the different sections, and I create a ColumnText object for each language:
PdfContentByte cb = writer.getDirectContent();
ColumnText[] columns = new ColumnText[3];
for (int section = 1; section <= 3; section++) {
for (int la = 0; la < 3; la++) {
columns[la] = createColumn(cb, section, LANGUAGES[la], RECTANGLES[la]);
}
while (addColumns(columns)) {
document.newPage();
for (int la = 0; la < 3; la++) {
columns[la].setSimpleColumn(RECTANGLES[la]);
}
}
document.newPage();
}
If you examine the body of the inner loop, you see that I first define three ColumnText objects, one for each language:
public ColumnText createColumn(PdfContentByte cb, int i, String la, Rectangle rect)
throws IOException {
ColumnText ct = new ColumnText(cb);
ct.setSimpleColumn(rect);
Phrase p = createPhrase(String.format("resources/text/liber1_%s_%s.txt", i, la));
ct.addText(p);
return ct;
}
In this case, I'm using ColumnText in text mode, and I read the text from the different files into a Phrase like this:
public Phrase createPhrase(String path) throws IOException {
Phrase p = new Phrase();
BufferedReader in = new BufferedReader(
new InputStreamReader(new FileInputStream(path), "UTF8"));
String str;
while ((str = in.readLine()) != null) {
p.add(str);
}
in.close();
return p;
}
Once I have defined the ColumnText objects and added their content, I need to render the content to one of more pages until all the text is rendered from all columns. To achieve this, we use this method:
public boolean addColumns(ColumnText[] columns) throws DocumentException {
int status = ColumnText.NO_MORE_TEXT;
for (ColumnText column : columns) {
if (ColumnText.hasMoreText(column.go()))
status = ColumnText.NO_MORE_COLUMN;
}
return ColumnText.hasMoreText(status);
}
As you can see, I also create a new page for every new section I start. This isn't really necessary: I could add all the section to a single ColumnText, but depending on how the Latin text translated into English and French, you could end up with large discrepancies where section X of the Latin text starts on one page and the same section in English or French starts on another page. Hence my choice to start a new page, although it's not really necessary in this small proof of concept.
Using OnEndPage, I add a footer to my PDF created with iTextSharp. The footer font gets progressively bolder with each page.
How can I create consistent NORMAL fonts in my footer?
Here is my code:
public override void OnEndPage(PdfWriter writer, Document doc)
{
iTextSharp.text.Image gif = null;
if (FooterImage)
{
if (File.Exists(PathImages))
{
gif = iTextSharp.text.Image.GetInstance(PathImages);
gif.ScaleToFit(75f, 75f);
gif.SetAbsolutePosition(0, 0);
}
}
string sFooter = string.Empty;
if (FooterURL != null && FooterURL.Length > 0)
{
sFooter = FooterURL + " ";
}
if (FooterDate != null && FooterDate.Length > 0)
{
sFooter += FooterDate + " ";
}
if (FooterPage)
{
sFooter += "Page " + doc.PageNumber.ToString();
}
PdfPTable footerTbl = new PdfPTable(1);
footerTbl.TotalWidth = 900;
footerTbl.HorizontalAlignment = Element.ALIGN_CENTER;
Phrase ph = new Phrase(sFooter, FontFactory.GetFont(FontFactory.TIMES, 10, iTextSharp.text.Font.NORMAL));
PdfPCell cell = new PdfPCell(ph);
cell.Border = 0;
cell.PaddingLeft = 10;
footerTbl.AddCell(cell);
if (FooterImage)
{
PdfContentByte cbfoot = writer.DirectContent;
PdfTemplate tpl = cbfoot.CreateTemplate(gif.Width / 5, gif.Height / 5);
tpl.AddImage(gif);
cbfoot.AddTemplate(tpl, doc.PageSize.Width - 100, 10);
}
footerTbl.WriteSelectedRows(0, -1, 10, 30, writer.DirectContent);
}
In the old days, when there wasn't as much choice as today regarding fonts, people used workarounds to create bold fonts. One way to make a font bold, was by adding the same text over and over again at the same position. I think that this is happening to you.
When you use page events correctly, the onEndPage() method is triggered automatically each time a page ends. My guess is that you're doing something very wrong that triggers the onEndPage() many times. Maybe you are called the onEndPage() from your code, maybe you're adding the page event to the writer more than once (and page events are cumulative).
If I have to guess, I would guess that you are doing the latter. My guess is based on the fact that you are using variables such as FooterImage in your onEndPage() method. How are you setting that variable. If you are setting it in the constructor of the page event and you're adding the new page event over and over again to the writer, then you're doing it wrong.