I'm using a fixed cell height to create a table.
If the font size is too large, the text is not visible in the table.
Is there a built-in function in iText that automatically reduces the font size to the maximum possible size, or do I have to implement this by myself?
Automatic font size is only possible in the context of AcroForm text fields. When you define the font size of a text field as 0, then a font size is chosen that fits the rectangle. In the case of a fixed cell height in a table, you are responsible to make sure that the text fits.
If you're concerned about the height, please take a look at the FitTextInRectangle example:
BaseFont bf = BaseFont.createFont();
int textHeightInGlyphSpace = bf.getAscent(text) - bf.getDescent(text);
float fontSize = 1000f * fixedHeight / textHeightInGlyphSpace;
This example was written in answer to Correct text position center in rectangle iText
If you're concerned about the width, then you need to use the getWidthPoint() method as explained here: How to calculate the string width in iText?
BaseFont bf = BaseFont.createFont();
float width = bf.getWidthPoint("My text", myFontSize);
You'll need to make sure that width doesn't exceed the width of the cell. To achieve this, you'll need to adjust myFontSize.
See my answer to this question: How to choose the optimal size for a font?
Related
I created a table with iText 5.5.13.2 (latest iText5 version) and I'm filling it with text and images that are read from a specific folder on the same PC:
Paragraph p = new Paragraph();
p.add(new Phrase("This is a new paragraph!"));
PdfPTable table = new PdfPTable(2);
table.setWidthPercentage(100);
for(int i=0;i<imageArr.size();i++) { //imageArr.size()%2==0!
PdfPCell cell = new PdfPCell();
String name = imageArr.get(i);
String path = imgFolder + File.separator + name;
File f = new File(path);
if(f.isFile()) {
Image img = Image.getInstance(path);
//cell.setCalculatedHeight(50);
cell.addElement(img);
} else {
cell.addElement(new Phrase(name));
}
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_MIDDLE);
//cell.setCalculatedHeight(50);
table.addCell(cell);
}
p.add(table);
doc.add(p);
Both columns in the table use the same width (which is great) and big images are automatically resized to fit the width (which is also great), the only thing that's not working:
The cells should all be a certain height and the big images should resize accordingly (while still keeping the proper height/width ratio). It doesn't seem to matter if I use setCalculatedHeight before or after I add the image to the cell (only doing one or the other, see code above), the cell always sets its height according to the image's height, so rows with only text or images in landscape format are always smaller than rows with images in portrait format.
Small images are also resized (stretched), even while using img.setScaleToFitLineWhenOverflow(false) and img.setScaleToFitHeight(false), but even then the height isn't set properly.
I already tried to use a Chunk (cell.addElement(new Chunk(img, 0, 0))) but then the images are tiny and my height setting is still ignored.
How do I set the cell's height and make the images resize accordingly?
I managed to find a solution. A lot of testing was involved and even now I'm not 100% sure why it was behaving the way it did most of the time.
First of all: Do not add the element with addElement because once you call this with a PdfPCell, iText switches from "Text Mode" (seems to affect images too) to "Composite Mode" and from then on ignores all the alignment,... settings for that cell, including horizontal alignment for text - you can find a more detailed explanation by the original iText developer here (with examples here). Instead do whatever you want to do with the Image first and only then create the PdfPCell with that image. Afterwards the cell can be edited - using the table.getDefaultCell() won't work though, the changes to it won't have any effect on the cells created with the code below.
My working code:
float docWidth = doc.getPageSize().getWidth() - doc.leftMargin() - doc.rightMargin();
float docHeight = doc.getPageSize().getHeight() - doc.topMargin() - doc.bottomMargin();
float docWidthDiv2 = docWidth/2 - 10;
float docHeightDiv2 = docHeight/2 - 10;
PdfPCell cell = null;
if(f.isFile()) {
Image img = Image.getInstance(path);
//img.scaleAbsolute(100, 50);
if(img.getWidth() < docWidthDiv2 && img.getHeight < docHeightDiv2) {
cell = new PdfPCell(img, false);
} else {
cell = new PdfPCell(img, true);
}
} else {
cell = new PdfPCell(new Phrase(name));
}
cell.setFixedHeight(50); //"setCalculatedHeight" doesn't work
Why do I compare the image's width to docWidthDiv2 and the image's height to docHeightDiv2?
There are a lot of combinations for setting the cell's height but none show 100% of the behavior I expected: Really big images should be scaled down to fit the width of the column (more important for images in landscape mode) but also respect the cell's fixed height (more important for images in portrait mode), while still keeping their aspect ratio. Small images that already fit the cell comfortably should not be scaled at all.
The documentation for new PDfPCell(Image image, boolean fit) describes the fit parameter with:
true to fit the image to the cell
In my case true resizes the image (while still respecting its aspect ratio and the cell's height) until it touches two opposite sides of the cell, hence: Big images are reduced in size and small images are stretched.
With false the aspect ratio of the image and the cell's height are still respected but while small images keep their size, big images in landscape mode "bleed" into the neighboring cell (and setScaleToFitLineWhenOverflow doesn't help) and big images in portrait mode might not even be displayed at all (when they're too tall for the cell).
To not stretch small images but decrease the size of big images, a combination of both is needed. I only added the -10, so a potential default padding won't mess with it. If you want to add text before or after the table, then you have to deduct its height from docHeightDiv2 too.
As mentioned, there are also other combinations I tested, the most important information I took away from it:
If the cell's height is set before the image is added, then the image'll overwrite the height, no matter if it's smaller (cell shrinks in height) or bigger (cell's height increases) than the cell.
There are a couple of combinations that can be used, between the parameter, setting the image size and setting the cell's height but with most of them the images either keep their original size (e.g. 2000x1000 won't be completely visible on the page) or they're increased in size until they touch two opposite sides of the cell (which also increases the height of the cell). In the end there's only one combination left that's still useful (in my opinion) - an example:
img.scaleAbsolute(100, 50);
cell = new PdfPCell(img, false);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE); //"center" doesn't work here
cell.setHorizontalAlignment(Element.ALIGN_CENTER); //"middle" doesn't work here
cell.setFixedHeight(150);
This'll create an image with a size of 100x50 (the original aspect ratio is ignored) in the center of a cell that's 150 units tall (= padding of 50 units above and below the image).
Additional information about iText's table:
Columns share the available width of the table equally and there's no need to change it, even if the first cell contains a really small image and the second a really big one. The only thing that you have to pay attention to, in that regard, is the number of cells that are added - rows always have to be completely filled, so a table with 3 columns has to contain 3 cells per row, otherwise that row won't be printed into the pdf file (the same way an empty new page also won't be printed). It's possible to create empty extra cells to fill the rest of the row:
PdfPCell extra = new PdfPCell();
extra.setFixedHeight(50);
table.addCell(extra);
I'm using a JTextPane to display a kind of bus ticket that uses a monospaced font. I am using StyleConstants and SimpleAttributeSetto set bold and italic text, but I would like to also use double width and height characters. I don't want to make a double-sized font, by double I mean that a character 'A' which I want to be double-width, would get stretched in width, but not in height, and vice versa for double-height, like in this image I found here.
I really don't know any method to do so and googling didn't give me an answer. Maybe a way to adjust the size of a font horizontally and vertically?
To adjust the size of a font to double width:
// Input: an existing Font object called 'font'.
final int style = Font.PLAIN;
AffineTransform transform = new AffineTransform();
transform.setToScale(2.0, 1.0);
Font doubleWidthFont = font.deriveFont(style, transform);
I haven't tried this code, so please let me know if it doesn't work exactly as written.
Even though it's for JEditorPane, but also should work with JTextPane
http://java-sl.com/tip_text_height_measuring.html
I am implementing PdfPageEventHelper event and footer stuff is as below:
ColumnText.showTextAligned(cb, Element.ALIGN_RIGHT, new Phrase(String.format(" %d ",
writer.getPageNumber()),footerFont),
document.right() - 2 , document.bottom() - 20, 0);
Now, i have 3 lines which needs to be added into footer but i don't find a best to set its vertical margin. (Each 3 LINES has different font SIZE).
what should keep for - document.bottom() - XXX ??
The difference between two lines is the leading. You can pick your own leading, but it is custom to use 1.5 times the font size. You are drawing line by line yourself, using different font sizes, so you'll have to adjust the Y value based on that font size. Note that ColumnText.showTextAligned() uses the Y value as the baseline of the text you're adding, so if you have some text with font size of 12pt, you'd need to take into account a leading of 18pt. If you have a font size 8pt, you make sure you have 12pt.
That's the easy solution: based on "convention". If you really want to know how much horizontal space some specific takes, you need to calculate the ascender and the descender, as is done in figure 3.7 of my book. You'll find the code here. If bf is your font (a BaseFont object), text is your text (a String) and size is your font size (a float), then the height of your text is equal to height:
float aboveBaseline = bf.getAscentPoint(text, size);
float underBaseline = bf.getDescentPoint(text, size);
float height = aboveBaseline - underBaseline;
When y is the Y-coordinate used in showTextAligned() make sure you keep the space between y + aboveBaseline and y + underBaseline free. This is the accurate solution.
Note that document.bottom() - 20 looks somewhat strange. I would expect document.bottom() + 20 as the Y-axis of the PDF coordinate system points upwards, not downwards.
In Java when the text of the JLabel could not be displayed due to lack of space the text is truncated and "..." is added in the end.
How can I easily find out if currently JLabel displays full text or the truncated?
EDIT:
I see that there is a way to find out the size of the text by using FontMetrics. However this solution doesn't fully answers the question. In the case the text of JLabel contains HTML decorations the metrics.stringWidth() would also calculate width of HTML tags. So it could happen that result of metrics.stringWidth() would be grater than JLabel's width but still the text would be displayed correctly.
Is there a way know what decision took the JLabel itself while displaying the text. Has it decided to truncate the text or not.
The ellipsis is added by the label's UI delegate, typically a subclass of BasicLabelUI, as part of it's layout and preferred size calculation. The method layoutCL() may be overridden to examine the geometry, as shown on this example.
As a practical matter, I'd ignore the elision and show the full text in a tool tip.
From Oracle - Measuring Text:
// get metrics from the graphics
FontMetrics metrics = graphics.getFontMetrics(font);
// get the height of a line of text in this font and render context
int hgt = metrics.getHeight();
// get the advance of my text in this font and render context
int adv = metrics.stringWidth(text);
// calculate the size of a box to hold the text with some padding.
Dimension size = new Dimension(adv+2, hgt+2);
Compare size to the size of the JLabel.getSize();
I suppose if the component's preferred size is greater than it's actual size, then you can expect truncation. In order for this to work, of course, the component must already be realized.
Check this and see the layoutCompoundLabel() method. It returns a String representing the text of the label. You can compare it to the original to determine if it will be clipped.
Jim S.
The usual way to do this is to use a method that calculates the expected size of the text as it will be displayed in the label. If you're using a monospaced font, this is easy:
lengthOfChar * numChars
If you're not using a monospaced font, it's obviously much harder. I think there are some utilities around that will attempted to calculate this.
Once you have the size of the displayed string, you can compare to the length of the JLabel and see if the label is too small.
To get sizes of html text check this https://www.java.net/node/665691.
View view = (View) javax.swing.plaf.basic.BasicHTML.createHTMLView(label, value.toString());
int width = (int) view.getPreferredSpan(View.X_AXIS);
int height = (int) view.getPreferredSpan(View.Y_AXIS);
The only small problem is that it might have issues with non-html text. So, just use font metrics for non-html strings. The following worked perfectly for me:
if (value.toString().startsWith("<html>")) {
View view = (View) javax.swing.plaf.basic.BasicHTML.createHTMLView(label, value.toString());
width = (int) view.getPreferredSpan(View.X_AXIS);
}
else {
width = (int) label.getFontMetrics(label.getFont()).stringWidth(value.toString());
}
From an xml file, I'm given a width, height and id. All of them can and do vary very quickly. Now, I'm asked to draw a rectangle using the width and height (an easy task), and place the id at its center. The id must not overflow out of the rectangle it's contained it.
For single-character strings, this is also easy - set the font size to the height, play a bit with the x position maybe, and it's centered. The problem is when it's multi-character strings.
So given a width and height and a string, how can you determine what font-size the string should appear in? Assume you have every bit of information you need on the rectangle you're drawing the string in.
[Edit]: I'm using the Graphics 2D class to draw everything.
Start with selecting a Font at your preferred (i.e. maximum) size.
Grab the FontRenderContext from your Graphics2D object using getFontRenderContext.
Use getStringBounds() on the Font to be rendered to get a Rectangle2D object for the specific String to be rendered. That object describes the final size of the String using that Font
Check if the size specified by that Rectangle2D is small enough.
4a. If it is small enough, you're done. Use the last Font you've checked.
4b. If it is too big, use Font.derive() to produce a smaller version of the Font and continue to use that and loop back to 3.
Don't quite have the time to give you a full working example, but here are a couple pointers that should get you going in the right direction. The graphics object you are using to draw with has a getFontMetrics() method, one of the methods on FontMetrics is stringWidth(String str) which gives you the width of a string in the current Font.
If the width is too big for your rectangle set the Font on the Graphics object to the same font just with a smaller size until it fits.
To horizontally center a string in a container (learned long ago in typing class in high school):
(rectangleWidth / 2) - (stringWidth / 2)
http://download.oracle.com/javase/1.5.0/docs/api/java/awt/FontMetrics.html
To create a Font with a smaller size, something like:
Font font = graphics.getFont();
Font smallerFont = font.derive(font.getSize() - 1);
graphics.setFont(smallerFont);
Hope this gets you going in the right direction.
I would recommend for this problem to remove as many unknowns as possible. In this case, the problem chiefly is that font characters can vary in width... well most. That's why I would use a good monospace font like courier new for the ID, that way you know what the width of each character is, you know the width of your rectangle and you know the number of characters in your string. You can simply reduce the pixel size of each character will till your string fits the available width.
Example, if the width of each character is 12px and you have 10 characters in your ID, then you need 120px to fit everything in. If you only have 80px available, it's simple math 80/10 = 8px font-size (reduce half a pixel for padding if you want.
Just my suggestion.