Would it be possible to get the width of a string based on its font?
So for example, if the font size is 40, and the string was "hi", could you do 2*40.
An example of this would be
startX = 260; startWidth = "Start".length()*getFont().getSize();
startY = getHeight()-startWidth-20;
startHeight = getFont().getSize();
It really depends on the type of font, whether it's monospaced or proportional. Most font now are proportional so you would not simply be able to calculate num_of_chars * n.
The gui interface you are using should give you the width of the container once it has been populated with the given text.
Related
I have an assignment right now about OpenStreetMaps, where one of the exercises is to display the road names at their respective roads on the map.
My problem right now, is that the coordinates we're using are so small, that even the smallest int font size is hundred times larger than what it's supposed to be.
I have tried the method deriveFont(), but it doesn't seem to have any effect.
g.setPaint(Color.black);
for (DrawnString d : model.getRoads()){
Point2D.Double p = d.getPosition();
Font font = new Font("TimesRoman", Font.PLAIN, 1);
font.deriveFont(0.0001f); //doesn't work!
g.setFont(font);
g.drawString(d.getText(), (float) p.x, (float) p.y);
}
My question is, if there's a way to decrease the font size to a small size like 0.0001f?
The deriveFont() method returns an object of type font that is a replica of the calling font with changed parameters.
So change the line to: font = font.deriveFont(0.001f); and everything works just as expected (with very tiny font)
Okay it's me who's stupid, I just missed a "font =" in front of derivedFont().
font = font.deriveFont(0.0001f);
It works now.
There is a method in PDFBox's font class, PDFont, named getFontHeight which sounds simple enough. However I don't quite understand the documentation and what the parameters stand for.
getFontHeight
This will get the font width for a character.
Parameters:
c - The character code to get the width for.
offset - The offset into the array. length
The length of the data.
Returns: The width is in 1000 unit of text space, ie 333 or 777
Is this method the right one to use to get the height of a character in PDFBox and if so how? Is it some kind of relationship between font height and font size I can use instead?
I believe the answer marked right requires some additional clarification. There are no "error" per font for getHeight() and hence I believe it is not a good practice manually guessing the coefficient for each new font.
Guess it could be nice for your purposes simply use CapHeight instead of Height.
float height = ( font.getFontDescriptor().getCapHeight()) / 1000 * fontSize;
That will return the value similar to what you are trying to get by correcting the Height with 0.865 for Helvetica. But it will be universal for any font.
PDFBox docs do not explain too much what is it. But you can look at the image in the wikipedia Cap_height article to understand better how it is working and choose the parameter fit to your particular task.
https://en.wikipedia.org/wiki/Cap_height
EDIT: Cap height was what I was looking for. See the accepted answer.
After digging through the source of PDFBox I found that this should do the trick of calculating the font height.
int fontSize = 14;
PDFont font = PDType1Font.HELVETICA;
font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize
The method isn't perfect though. If you draw a rectangle with the height 200 and a Y with the font size 200 you get the font height 231.2 calculated with the above method even though it actually is printed smaller then the rectangle.
Every font has a different error but with helvetica it is close to 13.5 precent too much independently of font size. Therefore, to get the right font height for helvetica this works...
font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize * 0.865
Maybe use this?
http://pdfbox.apache.org/apidocs/org/apache/pdfbox/util/TextPosition.html
Seems like a wrap-around util for text. I haven't looked in the source if it accounts for font error though.
this is a working method for splitting the text and finding the height
public float heightForWidth(float width) throws IOException {
float height = 0;
String[] split = getTxt().split("(?<=\\W)");
int[] possibleWrapPoints = new int[split.length];
possibleWrapPoints[0] = split[0].length();
for (int i = 1; i < split.length; i++) {
possibleWrapPoints[i] = possibleWrapPoints[i - 1] + split[i].length();
}
float leading = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
int start = 0;
int end = 0;
for (int i : possibleWrapPoints) {
float w = font.getStringWidth(getTxt().substring(start, i)) / 1000 * fontSize;
if (start < end && w > width) {
height += leading;
start = end;
}
end = i;
}
height += leading;
return height + 3;
}
For imported True Type Fonts the total height of the font is
(org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getDescent() + org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getAscent() + org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getLeading()) * point size * org.apache.pdfbox.pdmodel.font.PDFont.getFontMatrix().getValue(0, 0)
You will find that font.getFontDescriptor().getFontBoundingBox().getHeight() is 20% larger than the above value as it includes a 20% leading on the above value, but if you take the top value and remove 20%, the font will be right next too each other
It’s easy to determine the rendered height of a font using FontMetrics, but what about the other way around? How can I obtain a font that will fit into a specific height in pixels?
"Give me Verdana in a size that is 30 pixels high from ascender to descender."
How do I ask Java for this?
I know this is a very old question, but someone might still find it:
The font height in Java (and many other places) is given in "typographic points", which are defined as roughly 1/72nd of an inch.
To calculate the points needed for a certain pixel height, you should be able to use the following:
double fontSize= 72.0 * pixelSize / Toolkit.getDefaultToolkit().getScreenResolution();
I haven't tested this extensively yet, but it seems to work for the monitors that I've used. I'll report back if I ever find a case where it doesn't work.
For the standard system fonts I've used this with, this sets the height of a capital letter (i.e. the ascent) to the provided pixel size. If you need to set the ascent+descent to the pixel size, you can correct the value using the FontMetrics:
FontMetrics m= g.getFontMetrics(font); // g is your current Graphics object
double totalSize= fontSize * (m.getAscent() + m.getDescent()) / m.getAscent();
Of course, the actual pixel-height of some specific letters will depend on the letter and the font used, so if you want to make sure that your "H" is some exact number of pixels tall, you might still want to use the trial-and-error methods mentioned in the other answers. Just keep in mind that if you use these methods to get the size for each specific text you want to display (as #Bob suggested), you might end up with a random font-size-mess on your screen where a text like "ace" will have much bigger letters than "Tag". To avoid this, I would pick one specific letter or letter sequence ("T" or "Tg" or something) and fix that one to your pixel height once and then use the font size you get from that everywhere.
I don't think there's a "direct" way to find a font by height; only an indirect way... by looping through the sizes, and testing the height of each is <= required height.
If you're doing this once, just loop through them... if you've doing it "on the fly" then do a binary search, it'll be quicker.
I'm not aware of a way to get a font by its actual height in pixels. It depends on the context it's used in so there's probably no shorter way than to sample for the best match. It should be pretty quick to look for sizes up or down from the designed height. Here's an example method that does that:
public Font getFont(String name, int style, int height) {
int size = height;
Boolean up = null;
while (true) {
Font font = new Font(name, style, size);
int testHeight = getFontMetrics(font).getHeight();
if (testHeight < height && up != Boolean.FALSE) {
size++;
up = Boolean.TRUE;
} else if (testHeight > height && up != Boolean.TRUE) {
size--;
up = Boolean.FALSE;
} else {
return font;
}
}
}
WhiteFang34's code is useful in combination with the following method that returns the actual height of a specific string. It might be a bit slow for real-time rendering, especially for large fonts/strings and I'm sure it can be further optimised, but for now it meets my own needs and is fast enough to run in a back-end process.
/*
* getFontRenderedHeight
* *************************************************************************
* Summary: Font metrics do not give an accurate measurement of the rendered
* font height for certain strings because the space between the ascender
* limit and baseline is not always fully used and descenders may not be
* present. for example the strings '0' 'a' 'f' and 'j' are all different
* heights from top to bottom but the metrics returned are always the same.
* If you want to place text that exactly fills a specific height, you need
* to work out what the exact height is for the specific string. This method
* achieves that by rendering the text and then scanning the top and bottom
* rows until the real height of the string is found.
*/
/**
* Calculate the actual height of rendered text for a specific string more
* accurately than metrics when ascenders and descenders may not be present
* <p>
* Note: this method is probably not very efficient for repeated measurement
* of large strings and large font sizes but it works quite effectively for
* short strings. Consider measuring a subset of your string value. Also
* beware of measuring symbols such as '-' and '.' the results may be
* unexpected!
*
* #param string
* The text to measure. You might be able to speed this process
* up by only measuring a single character or subset of your
* string i.e if you know your string ONLY contains numbers and
* all the numbers in the font are the same height, just pass in
* a single digit rather than the whole numeric string.
* #param font
* The font being used. Obviously the size of the font affects
* the result
* #param targetGraphicsContext
* The graphics context the text will actually be rendered in.
* This is passed in so the rendering options for anti-aliasing
* can be matched.
* #return Integer - the exact actual height of the text.
* #author Robert Heritage [mrheritage#gmail.com]
*/
public Integer getFontRenderedHeight(String string, Font font, Graphics2D targetGraphicsContext) {
BufferedImage image;
Graphics2D g;
Color textColour = Color.white;
// In the first instance; use a temporary BufferedImage object to render
// the text and get the font metrics.
image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
g = image.createGraphics();
FontMetrics metrics = g.getFontMetrics(font);
Rectangle2D rect = metrics.getStringBounds(string, g);
// now set up the buffered Image with a canvas size slightly larger than
// the font metrics - this guarantees that there is at least one row of
// black pixels at the top and the bottom
image = new BufferedImage((int) rect.getWidth() + 1, (int) metrics.getHeight() + 2, BufferedImage.TYPE_INT_RGB);
g = image.createGraphics();
// take the rendering hints from the target graphics context to ensure
// the results are accurate.
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, targetGraphicsContext.getRenderingHint(RenderingHints.KEY_ANTIALIASING));
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, targetGraphicsContext.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));
g.setColor(textColour);
g.setFont(font);
g.drawString(string, 0, image.getHeight());
// scan the bottom row - descenders will be cropped initially, so the
// text will need to be moved up (down in the co-ordinates system) to
// fit it in the canvas if it contains any. This may need to be done a
// few times until there is a row of black pixels at the bottom.
boolean foundBottom, foundTop = false;
int offset = 0;
do {
g.setColor(Color.BLACK);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
g.setColor(textColour);
g.drawString(string, 0, image.getHeight() - offset);
foundBottom = true;
for (int x = 0; x < image.getWidth(); x++) {
if (image.getRGB(x, image.getHeight() - 1) != Color.BLACK.getRGB()) {
foundBottom = false;
}
}
offset++;
} while (!foundBottom);
System.out.println(image.getHeight());
// Scan the top of the image downwards one line at a time until it
// contains a non-black pixel. This loop uses the break statement to
// stop the while loop as soon as a non-black pixel is found, this
// avoids the need to scan the rest of the line
int y = 0;
do {
for (int x = 0; x < image.getWidth(); x++) {
if (image.getRGB(x, y) != Color.BLACK.getRGB()) {
foundTop = true;
break;
}
}
y++;
} while (!foundTop);
return image.getHeight() - y;
}
I need text to fill a predefined area. I don't necessarily want the fonts to grow beyond a certain limit, but I would prefer that it at least shrinks if it doesn't fit in a certain area. (So to have an upperbound on the font size, and also a lowerbound, but take the upperbound as default and scale down when required.)
Is there any easy way to get something like that done easily?
I eventually ended up using the fitText operation on PdfSignatureAppearance, included in iText. That made it quite easy to get it done. It may not be perfect, but it's much better than not having anything at all.
public static final float calculateFontSize(String fontName, float maxFontsize, float width, float height, String text) {
Font font = FontFactory.getFont(fontName);
Rectangle rectangle = new Rectangle(width, height);
System.err.println("Calculating font size for " + width + " and " + height);
return PdfSignatureAppearance.fitText(font, text, rectangle, maxFontsize, PdfWriter.RUN_DIRECTION_LTR);
}
I would hand pick the font sizes you want to use to make sure they look good on the target output. No reason to stick to 3, use as many as you want. The rectangle passed into getFont() must have x an y both set to 0.
public class FontFactory {
private Font[] fonts;
public FontFactory() {
fonts = new Font[3];
// font[0] = largest font
// font[1] = middle font
// font[2] = smallest font
}
public Font getFont(String string, Graphics g, Rectangle rectangle) {
for (Font font : fonts) {
Rectangle2D bounds = g.getFontMetrics(font).getStringBounds(string, g);
if (rectangle.contains(bounds)) {
return font;
}
}
return fonts[fonts.length - 1];
}
}
Is it possible to change the unit for Paint.setTextSize()? As far as I know, it's pixel but I like to set the text size in DIP for multiple screen support.
I know this topic is old and already answered but I would like to also suggest this piece of code:
int MY_DIP_VALUE = 5; //5dp
int pixel= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
MY_DIP_VALUE, getResources().getDisplayMetrics());
Convert it like this
// The gesture threshold expressed in dip
private static final float GESTURE_THRESHOLD_DIP = 16.0f;
// Convert the dips to pixels
final float scale = getContext().getResources().getDisplayMetrics().density;
mGestureThreshold = (int) (GESTURE_THRESHOLD_DIP * scale + 0.5f);
// Use mGestureThreshold as a distance in pixels
from here http://developer.android.com/guide/practices/screens_support.html#dips-pels
The accepted answer is for gestures, not setting text size. The highest voted answer (at the time of this writing) is close, but the documentation recommends using sp rather than dp because in addition to being scaled for screen densities (as dp values are), sp is also scaled according to user preferred font sizes.
From an int in code
int spSize = 17;
float scaledSizeInPixels = spSize * getResources().getDisplayMetrics().scaledDensity;
mTextPaint.setTextSize(scaledSizeInPixels);
Or alternatively
int spSize = 17;
float scaledSizeInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spSize, getResources().getDisplayMetrics());
mTextPaint.setTextSize(scaledSizeInPixels);
From resources
Or if you have the sp or dp value in resources:
<resources>
<dimen name="fontSize">17sp</dimen>
</resources>
with
float scaledSizeInPixels = getResources().getDimensionPixelSize(R.dimen.fontSize);
mTextPaint.setTextSize(scaledSizeInPixels);
Other links
How to convert DP, PX, SP among each other, especially DP and SP?
Android: Canvas.drawText() text size on different screen resolutions
Paint.setTextSize
getDimensionPixelSize
And here is even shorter method to convert dp-s to px-els taking display metrics into account
https://developer.android.com/reference/android/content/res/Resources.html#getDimensionPixelSize(int)
If your Paint object is being used to draw text on a Canvas, you can let the Canvas handle scaling for you.
When calling Canvas.drawText() the text size is first determined by the passed in Paint object, which can be set via Paint.setTextSize(). The text size is automatically scaled by Canvas based on the canvas density, which can be found using Canvas.getDensity().
When setting the text size on a paint object that will be drawn on Canvas, work with a unit value of dp or sp and let Canvas handle the scaling for you.