How do I add a rectangle (or other graphical elements) as inline-elements to an iText PDF?
Example code of what I'm trying to achieve:
foreach (Row r in entrylist)
{
p = new Paragraph();
p.IndentationLeft = 10;
p.SpacingBefore = 10;
p.SpacingAfter = 10;
p.Add(new Rectangle(0, 0, 10, 10)); <<<<<<<<< THAT ONE FAILS
p.Add(new Paragraph(r.GetString("caption"), tahoma12b));
p.Add(new Paragraph(r.GetString("description"), tahoma12));
((Paragraph)p[1]).IndentationLeft = 10;
doc.Add(p);
}
It's something like a column of text-blocks, of which each of them have (only a printed) checkbox.
I've tried various things with DirectContent, but it requires me to provide absolute X and Y values. Which I simply don't have. The elements should be printed at the current position, wherever that may be.
Any clues?
You need a Chunk for which you've defined a generic tag. For instance, in this example listing a number of movies, a snippet of pellicule is drawn around the year a movie was produced and an ellipse was drawn in the background of the link to IMDB.
If you look at the MovieYears example, you'll find out how to use the PdfPageEvent interface and its onGenericTag() method. You're right that you can't add a Rectangle to a Paragraph (IMHO that wouldn't make much sense). As you indicate, you need to draw the rectangle to the direct content, and you get the coordinates of a Chunk by using the setGenericTag() method. As soon as the Chunk is drawn on the page, its coordinates will be passed to the onGenericTag() method.
Related
This is my code to create appearance stream for a free text annotation.
cs.rectangle(bbox.getLeft() , bbox.getBottom(), bbox.getWidth(), bbox.getHeight());
cs.fill();
String[] text = new String[1];
text[0] = "BAC"
cs.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED), pdfJSAnnotation.getFontSize());
cs.beginText();
cs.setLeading(fontSize + 1.75f);
cs.moveText(0, bbox.getHeight() - fontSize + .75f);
for (String s : text) {
if (s.equals("\n"))
cs.newlineText();
else
cs.showText(s);
}
cs.endText();
where cs is PdfAppearance, bbox is Rectangle. This works okay when pdf is portrait. however, im having problems when it is in landscape, say if page rotation is 270.
The text shown is vertical. and even if i use cs.transform() to rotate, it does not even rotate properly. I also tried to save the state, do a rotate then display text and then call cs.restoreState() after cs.endText() but the outcome is still not correct.
any ideas?
the rectangle is correct since the 1st 2 lines where it fills a rectangle shape is correctly displayed. it is the text i am having problems with.
The solution to this is:
- set appearance dimension to (height,width) since it is 270 degrees.
Then in the PdfAppearance object:
translate it to (height, 0);
rotate to 270
translate to (-height,-height);
(Tried to remove this post but there is no option available)
I have a label (e.g. "A list of stuff") and some content (e.g. an actual list). When I add all of this to a PDF, I get:
A list of stuff: test A, test B, coconut, coconut, watermelons, apple, oranges, many more
fruites, carshow, monstertrucks thing
I want to change this so that the content is aligned like this:
A list of stuff: test A, test B, coconut, coconut, watermelons, apple, oranges, many more
fruites, carshow, monstertrucks thing, everything is startting on the
same point in the line now
In other words: I want the content to be aligned so that it every line starts at the same X position, no matter how many items are added to the list.
There are many different ways to achieve what you want: Take a look at the following screen shot:
This PDF was created using the IndentationOptions example.
In the first option, we use a List with the label ("A list of stuff: ") as the list symbol:
List list = new List();
list.setListSymbol(new Chunk(LABEL));
list.add(CONTENT);
document.add(list);
document.add(Chunk.NEWLINE);
In the second option, we use a paragraph of which we use the width of the LABEL as indentation, but we change the indentation of the first line to compensate for that indentation.
BaseFont bf = BaseFont.createFont();
Paragraph p = new Paragraph(LABEL + CONTENT, new Font(bf, 12));
float indentation = bf.getWidthPoint(LABEL, 12);
p.setIndentationLeft(indentation);
p.setFirstLineIndent(-indentation);
document.add(p);
document.add(Chunk.NEWLINE);
In the third option, we use a table with columns for which we define an absolute width. We use the previously calculated width for the first column, but we add 4, because the default padding (left and right) of a cell equals 2. (Obviously, you can change this padding.)
PdfPTable table = new PdfPTable(2);
table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
table.setTotalWidth(new float[]{indentation + 4, 519 - indentation});
table.setLockedWidth(true);
table.addCell(LABEL);
table.addCell(CONTENT);
document.add(table);
There may be other ways to achieve the same result, and you can always tweak the above options. It's up to you to decide which option fits best in your case.
I am trying to parse annotation data in XFDF and draw it using the iText Library. I want to draw the annotation like the attached image.
Following is my code to test the PdfAnnotation.createInk function, but it is not working after run the code.
I have Google and read the documents, but not much information provided. Any suggestions and advice? Thanks!
// step 1
Document document = new Document(PageSize.A4);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(destFile));
// step 3
document.open();
PdfContentByte pcb = new PdfContentByte(writer);
pcb.setColorStroke(BaseColor.RED);
Rectangle rect = new Rectangle(52.92f, 397.56f, 173.36f, 530.67f);
float[][] inkList = {{61.736111f,530.669250f},{61.295139f,525.820984f},{61.295139f,518.768860f},
{61.295139f,505.986969f},{61.295139f,490.560547f},{61.295139f,470.726562f},{59.972221f,452.214844f},
{57.767361f,434.143890f},{56.003471f,418.276703f},{53.357639f,404.172516f},{51.593750f,391.390625f},
{50.711807f,382.134766f},{49.829861f,376.845703f}
};
//float inkList[][] =
PdfAnnotation an = PdfAnnotation.createInk(writer, rect, "", inkList);
an.setPage(1);
an.setColor(BaseColor.RED);
an.setFlags(PdfAnnotation.FLAGS_PRINT);
writer.addAnnotation(an);
//Step 5
document.close();
The output PDF seemingly does not contain an annotation because the defined annotation is a mere collection of isolated points.
The float[][] parameter of PdfAnnotation.createInk corresponds to the InkList entry of the Ink annotation dictionary:
InkList array (Required) An array of n arrays, each representing a stroked path. Each
array shall be a series of alternating horizontal and vertical coordinates in
default user space, specifying points along the path. When drawn, the
points shall be connected by straight lines or curves in an
implementation-dependent way.
Thus, in case of the OPs array
float[][] inkList = {{61.736111f,530.669250f},{61.295139f,525.820984f},{61.295139f,518.768860f},
{61.295139f,505.986969f},{61.295139f,490.560547f},{61.295139f,470.726562f},{59.972221f,452.214844f},
{57.767361f,434.143890f},{56.003471f,418.276703f},{53.357639f,404.172516f},{51.593750f,391.390625f},
{50.711807f,382.134766f},{49.829861f,376.845703f}
};
we have a collection of 13 paths each of which contain only a single point. So, nothing is drawn.
If we combine all the points in a single path, though,
float[][] inkList = {{61.736111f,530.669250f,61.295139f,525.820984f,61.295139f,518.768860f,
61.295139f,505.986969f,61.295139f,490.560547f,61.295139f,470.726562f,59.972221f,452.214844f,
57.767361f,434.143890f,56.003471f,418.276703f,53.357639f,404.172516f,51.593750f,391.390625f,
50.711807f,382.134766f,49.829861f,376.845703f}
};
the result is this:
which looks like the left line of the "H" in the screenshot in the question.
I would like to find out information about the layout of text in a PdfPCell. I'm aware of BaseFont.getWidthPointKerned(), but I'm looking for more detailed information like:
How many lines would a string need if rendered in a cell of a given width (say, 30pt)? What would the height in points of the PdfPCell be?
Give me the prefix or suffix of a string that fits in a cell of a given width and height. That is, if I have to render the text "Today is a good day to die" in a specific font in a PdfPCell of width 12pt and height 20pt, what portion of the string would fit in the available space?
Where does iText break a given string when asked to render it in a cell of a given width?
This is with regard to iText 2.1.6. Thanks.
iText uses the ColumnText class to render content to a cell. This is explained in my book on page 98-99. This means that, just like with ColumnText, you need to make the distinction between text mode and composite mode.
In any case, ColumnText measures the width of the characters and tests if they fit the available width. If not, the text is split. You can change the split behavior in different ways: by introducing hyphenation or by defining a custom split character.
I've written a small proof of concept to show how you could implement custom "truncation" behavior. See the TruncateTextInCell example.
Instead of adding the content to the cell, I have an empty cell for which I define a cell event. I pass the long text "D2 is a cell with more content than we can fit into the cell." to this event.
In the event, I use a fancy algorithm: I want the text to be truncated in the middle and insert "..." at the place where I truncated the text.
BaseFont bf = BaseFont.createFont();
Font font = new Font(bf, 12);
float availableWidth = position.getWidth();
int contentLength = content.length();
int leftChar = 0;
int rightChar = contentLength - 1;
availableWidth -= bf.getWidthPoint("...", 12);
while (leftChar < contentLength && rightChar != leftChar) {
availableWidth -= bf.getWidthPoint(content.charAt(leftChar), 12);
if (availableWidth > 0)
leftChar++;
else
break;
availableWidth -= bf.getWidthPoint(content.charAt(rightChar), 12);
if (availableWidth > 0)
rightChar--;
else
break;
}
String newContent = content.substring(0, leftChar) + "..." + content.substring(rightChar);
PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS];
ColumnText ct = new ColumnText(canvas);
ct.setSimpleColumn(position);
ct.addElement(new Paragraph(newContent, font));
ct.go();
As you can see, we get the available width from the position parameter and we check how many characters match, alternating between a character at the start and a character at the end of the content.
The result is shown in the resulting PDF: the content is truncated like this: "D2 is a c... the cell."
Your question about "how many lines" can be solved in a similar way. The ColumnText class has a getLinesWritten() method that gives you that information. You can find more info about positioning a ColumnText object in my answer to your other question: Can I tell iText how to clip text to fit in a cell
Looking to create a bar chart with an irregular, colored threshold field in the background, so that each data point has its own individual set of min/max thresholds, which ultimately would look something like this: http://dcalvitti.webs.com/plant/SAMPLE.png
Looked at D3 examples like this one: http://bl.ocks.org/mbostock/4062844
Can the latter example be manipulated to look more like the image I created?
Thanks in advance..
The graph shown in your sample image is actually much easier than the linked example; for that, you don't need to create a clipping path and you don't need to draw the line twice with two different colours.
For drawing the coloured background, use an area-path generator, created with d3.svg.area(). Set the y0 accessor function to be extract your minimum value for each point in your data array, and the y1 accessor function to extract the maximum value.
Then draw the line overtop as a normal line graph with a d3.svg.line() path generator.
Working example, adapted from the fiddles in the comments: http://jsfiddle.net/h45CD/12/
(Note: I commented out half the dataset, since the "year" values were repeated, not sure what that was supposed to represent.)
Key code:
// Define the value line path generator
var line = d3.svg.line()
.x( function(d) { return x(d.year); } )
.y( function(d) { return y(d.temp); } );
// Define the area path generator
var area = d3.svg.area()
.x( function(d) { return x(d.year); } )
.y0( function(d) { return y(d.min); } )
.y1( function(d) { return y(d.max); } );
/* ... */
// Add the background area showing the historic range
svg.append("path")
.datum(data)
.attr("class", "historicRange")
.attr("d", area);
// Add the value line
svg.append("path")
.datum(data)
.attr("class", "dataline")
.attr("d", line);
Edit based on comments
If you do want a line that changes colour depending on historic values, as opposed to a line drawn overtop of a background range, the most straight-forward solution is probably to create a <pattern> element consisting of the different coloured regions, and use this to stroke the value line.
You'll want to familiarize yourself with the different options for the pattern element. This MDN tutorial has a good intro, or you could dive into the full W3 specs.
For this situation, we want the pattern to be sized and positioned relative to the coordinate system used for drawing the line, regardless of the size or shape of the line itself. That means we will be setting both the patternUnits and the patternContentUnits to be userSpaceOnUse. The height and width of the pattern will be the height and width of the plotting area.
Within the pattern we will draw the area that represents the max-min range, but we also need to draw separate areas, with different colours, for values above the max and values below the min. We can use the same area generator for each, but need to change the y0/y1 accessor functions each time.
Key code:
// Add the pattern showing the historic range
var pattern = defs.append("pattern")
.datum(data) //add the data to the <pattern> element
//so it will be inherited by each <path> we append
.attr({
"patternUnits":"userSpaceOnUse",
"patternContentUnits":"userSpaceOnUse",
"width": width,
"height": height
})
.attr("id", "strokePattern");
pattern.append("path")
.attr("class", "historicRange between")
.attr("d", area);
pattern.append("path")
.attr("class", "historicRange above")
.attr("d", area.y1( 0 )
.y0( function(d){return y(d.max);} )
);
pattern.append("path")
.attr("class", "historicRange below")
.attr("d", area.y1( function(d){return y(d.min);} )
.y0( height )
);
// Add the value line
plot.append("path")
.datum(data)
.attr("class", "dataline")
.attr("d", line)
.style("stroke", "url(#strokePattern)");
Working example: http://jsfiddle.net/h45CD/14/
I'm including a web page link with charts authored by myself based on AMCharts and with the help of that web site's founder. Contains several examples of the above question and more..
http://dcalvitti.webs.com/SAMPLE/NEWWEBINDEX.html
The charts provided are still being worked on. For example, AMcharts does have a function that clips the color of a line above/below a certain value which I didn't know about, so there is still work to be done. I spent many weeks on the charts and thought I'd share. I'm sure someone will find something new here down the road...