I'm currently writing a new Karaoke-FX-Generator using Java. Now I have a problem with the implementation of the TextExtents-Function: It returns the wrong string bounds for the Subtitle file.
Here's an example:
The red rectangle represents the bounds of the string calculated by my program while the red background are the bounds calculated by xy-vsfilter.
Does anyone know how to fix that. I'm trying several hours and I still don't get any further.
This is the current implementation of the function.
/**
* Calculates the text-extents for the given text in the specified
* style.
* #param style The style
* #param text The text
* #return The extents of the text.
*/
public TextExtents getTextExtents(AssStyle style, String text) {
// Reads the font object from the cache.
Font font = this.getFont(style.getFontname(), style.isBold(), style.isItalic());
// If the font is unknown, return null.
if (font == null)
return null;
// Add the font size. (Note: FONT_SIZE_SCALE is 64)
font = font.deriveFont((float) style.getFontsize() * FONT_SIZE_SCALE);
// Returns other values like ascend, descend and ext-lead.
LineMetrics metrics = font.getLineMetrics(text, this.ctx);
// Calculate String bounds.
Rectangle2D rSize = font.getStringBounds(text, this.ctx);
// Returns the text-extents.
return new TextExtents(
rSize.getWidth() / FONT_SIZE_SCALE,
rSize.getHeight() / FONT_SIZE_SCALE,
metrics.getAscent() / FONT_SIZE_SCALE,
metrics.getDescent() / FONT_SIZE_SCALE,
metrics.getLeading() / FONT_SIZE_SCALE
);
}
I partially solved the problem. LOGFONT.lfHeight and Java uses different unit for font sizes. As such, I had to convert the font-size of java to the "logical" units.
// I used this code to convert from pixel-size to "logical units"
float fontSize = 72F / SCREEN_DPI; // SCREEN_DPI = 96
Now I only have small differences.
Related
I need a function that gives me the resolutions of my individual monitors.
I do know that Toolkit.getDefaultToolkit().getScreenSize() gives me the cumulative resolution of all monitors, but to limit the size of some frames I draw I want to know the resolutions of the individual screens.
I tried using GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices() which returns an array of all screens, but I could not find any resolution information in there.
The header of the function should be something like
/**
* Returns the Dimension of all available screen devices in order of appearance in
* GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()
*
* #return the Dimensions of all available screen devices.
*/
public Dimension[] getScreenResolutions() {
[ . . . ]
}
You need to use GraphicsConfiguration. The documentation shows how to get the screen bounds.
I just found out the same, while Rob Spoor answered, so I'm gonna give a full answer to my question:
GraphicsDevice has a function getDefaultConfiguration() which returns a GraphicsConfiguration which has a getBounds() method which again returns the values as a Rectangle.
An implementation of my function header would therefore be:
/**
* Returns the Dimension of all available screen devices in order of appearance in
* GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()
*
* #return the Dimensions of all available screen devices.
*/
public Dimension[] getScreenResolutions() {
// step 1: get amount of screens and allocate space
int deviceAmount = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length;
Dimension[] dimensions = new Dimension[deviceAmount];
Rectangle[] rectangles = new Rectangle[deviceAmount];
// step 2: get values as Rectangle[]
for (int i = 0; i < deviceAmount; i++) {
rectangles[i] = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[i].getDefaultConfiguration().getBounds();
}
// step 3: convert to Dimension[]
for (int i = 0; i < rectangles.length; i++) {
dimensions[i] = new Dimension(r.getWidth(), r.getHeight());
}
// step 4: return
return dimensions;
}
I try to extract some text out of a PDF. For that I need to define a rectangle that contains the text.
I recognized that the coordinates may have a different meaning when I compare the coordinates from extraction of text to coordinates of drawing.
package MyTest.MyTest;
import org.apache.pdfbox.pdmodel.*;
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
import org.apache.pdfbox.text.*;
import java.awt.*;
import java.io.*;
public class MyTest
{
public static void main (String [] args) throws Exception
{
PDDocument pd = PDDocument.load (new File ("my.pdf"));
PDFTextStripperByArea st = new PDFTextStripperByArea ();
PDPage pg = pd.getPage (0);
float h = pg.getMediaBox ().getHeight ();
float w = pg.getMediaBox ().getWidth ();
System.out.println (h + " x " + w + " in internal units");
h = h / 72 * 2.54f * 10;
w = w / 72 * 2.54f * 10;
System.out.println (h + " x " + w + " in mm");
int X = 85;
int Y = 175;
int dX = 250;
int dY = 15;
// extract some text
st.addRegion ("a", new Rectangle (X, Y, dX, dY));
st.extractRegions (pg);
String text = st.getTextForRegion ("a");
System.out.println("text="+text);
// fill a rectangle
PDPageContentStream contents = new PDPageContentStream (pd, pg,AppendMode.APPEND, false);
contents.setNonStrokingColor (Color.RED);
contents.addRect (X, Y, dX, dY);
contents.fill ();
contents.close ();
pd.save ("x.pdf");
}
}
The text I extract (output of text= in the console) is not the text I overdraw with my red rectangle (generated x.pdf).
Why??
For testing try some PDF you already have. To avoid a lot of try/error in aiming for a rectangle with text in it use a file with a lot of text.
There are (at least) two issues in your approach:
Different coordinate systems
You use st.addRegion. Its JavaDoc comment tells us:
/**
* Add a new region to group text by.
*
* #param regionName The name of the region.
* #param rect The rectangle area to retrieve the text from. The y-coordinates are java
* coordinates (y == 0 is top), not PDF coordinates (y == 0 is bottom).
*/
public void addRegion( String regionName, Rectangle2D rect )
(Actually the whole text extraction apparatus of PDFBox uses its own coordinate system, and there already have been many questions on stack overflow because of irritations this caused.)
On the other hand contents.addRect does not use those "java coordinates". Thus, you have to subtract the y coordinate you use in text extraction from the maximum crop box y coordinate to get a coordinate for addRect.
Furthermore, the region rectangles have their anchor point at the top left while the regular PDF rectangles (like the one you define with contents.addRect) have it at the bottom left. Thus, you additionally have to add or subtract the rectangle height from the y coordinate.
Actually you may have to change the x coordinate, too. It is not mirrored but there may be a shift, the PDFBox text extraction coordinate system uses x=0 for the left page border but that is not necessarily the case in PDF user space. Thus, you may have to add the left border x coordinate of the crop box to your text extraction x coordinate.
Possibly changed coordinate system
In the page content stream the coordinate system may have been changed by applying a transformation to the current transformation matrix. As a result the coordinates in the instructions you append to it may have a different meaning than even outlined above.
To rule out such an effect, you should use a different PDPageContentStream constructor with an additional boolean resetContext parameter:
/**
* Create a new PDPage content stream.
*
* #param document The document the page is part of.
* #param sourcePage The page to write the contents to.
* #param appendContent Indicates whether content will be overwritten, appended or prepended.
* #param compress Tell if the content stream should compress the page contents.
* #param resetContext Tell if the graphic context should be reset. This is only relevant when
* the appendContent parameter is set to {#link AppendMode#APPEND}. You should use this when
* appending to an existing stream, because the existing stream may have changed graphic
* properties (e.g. scaling, rotation).
* #throws IOException If there is an error writing to the page contents.
*/
public PDPageContentStream(PDDocument document, PDPage sourcePage, AppendMode appendContent,
boolean compress, boolean resetContext) throws IOException
I.e. replace
PDPageContentStream contents = new PDPageContentStream (pd, pg,AppendMode.APPEND, false);
by
PDPageContentStream contents = new PDPageContentStream (pd, pg,AppendMode.APPEND, false, false);
I have a Font object and I need both the width and height of the font. I know that the getSize() returns the height of the font as the point-size of a font is generally the height, but I'm at a loss when it comes to determining the width of the font.
Alternatively, being able to determine the width of each specific character supported by the Font would also be an acceptable solution.
My best guess is that the width is specified when using the loadFont method, but the documentation does not specify whether the size parameter represents the width or the height of the font.
All fonts being used are mono-space fonts such as DejaVu Sans Mono, GNU FreeMono, and Lucida Sans Unicode.
I have run into the same problem, and there seems to be no easy way. My solution was to create a Text element with the correct font and check it's size:
double computeTextWidth(Font font, String text, double wrappingWidth) {
Text helper = new Text();
helper.setFont(font);
helper.setText(text);
// Note that the wrapping width needs to be set to zero before
// getting the text's real preferred width.
helper.setWrappingWidth(0);
helper.setLineSpacing(0);
double w = Math.min(helper.prefWidth(-1), wrappingWidth);
helper.setWrappingWidth((int)Math.ceil(w));
double textWidth = Math.ceil(helper.getLayoutBounds().getWidth());
return textWidth;
}
If I remember correctly the trick here is to set prefWidth to -1. See the JavaDoc.
FontMetrics metrics = Toolkit.getToolkit().getFontLoader().getFontMetrics(font);
float charWidth = metrics.computeStringWidth("a");
float charHeight = metrics.getLineHeight();
There were many posts regarding this problem, but i couldn't understand the answers given by people in there.
Like in this post: "How to change the size of the font of a JLabel to take the maximum size" the answer converts the font size to 14! But that is static and further in other answers; their whole output screen seems to increase.
I display certain numbers in a JLabel named "lnum", it can show numbers upto 3 digits but after that it shows like "4..." I want that if the number is able to fit in the label, it should not change its font size but if like a number is 4 digit, it should decrease the font size in such a way that it fits. NOTE: i do not want that the dimensions of the jLabel change. I just want to change the text in It.
Edit:
Here is what code i tried
String text = lnum.getText();
System.out.println("String Text = "+text);//DEBUG
Font originalFont = (Font)lnum.getClientProperty("originalfont"); // Get the original Font from client properties
if (originalFont == null) { // First time we call it: add it
originalFont = lnum.getFont();
lnum.putClientProperty("originalfont", originalFont);
}
int stringWidth = lnum.getFontMetrics(originalFont).stringWidth(text);
int componentWidth = lnum.getWidth();
stringWidth = stringWidth + 25; //DEBUG TRY
if (stringWidth > componentWidth) { // Resize only if needed
// Find out how much the font can shrink in width.
double widthRatio = (double)componentWidth / (double)stringWidth;
int newFontSize = (int)Math.floor(originalFont.getSize() * widthRatio); // Keep the minimum size
// Set the label's font size to the newly determined size.
lnum.setFont(new Font(originalFont.getName(), originalFont.getStyle(), newFontSize));
}else{
lnum.setFont(originalFont); // Text fits, do not change font size
System.out.println("I didnt change it hahaha");//DEBUG
}
lnum.setText(text);
I have a problem that many a times it doesn't work, like if the text is "-28885" it shows "-28...".
stringWidth = stringWidth + 25; //DEBUG TRY
I had to add this code so that it increases the length that it gets. It was a code i added to just temporarly fix the problem. I want a permanent solution for this.
Adapted from an answer on the question you referred to:
void setTextFit(JLabel label, String text) {
Font originalFont = (Font)label.getClientProperty("originalfont"); // Get the original Font from client properties
if (originalFont == null) { // First time we call it: add it
originalFont = label.getFont();
label.putClientProperty("originalfont", originalFont);
}
int stringWidth = label.getFontMetrics(originalFont).stringWidth(text);
int componentWidth = label.getWidth();
if (stringWidth > componentWidth) { // Resize only if needed
// Find out how much the font can shrink in width.
double widthRatio = (double)componentWidth / (double)stringWidth;
int newFontSize = (int)Math.floor(originalFont.getSize() * widthRatio); // Keep the minimum size
// Set the label's font size to the newly determined size.
label.setFont(new Font(originalFont.getName(), originalFont.getStyle(), newFontSize));
} else
label.setFont(originalFont); // Text fits, do not change font size
label.setText(text);
}
When you'll display a number that would fit, you should reset the Font back to its original (see the else part).
EDIT: If you can't/don't want to keep a reference to the original Font, you can save it as a "client property" (see the first lines).
I want to draw text on canvas of certain width using .drawtext
For example, the width of the text should always be 400px no matter what the input text is.
If input text is longer it will decrease the font size, if input text is shorter it will increase the font size accordingly.
Here's a much more efficient method:
/**
* Sets the text size for a Paint object so a given string of text will be a
* given width.
*
* #param paint
* the Paint to set the text size for
* #param desiredWidth
* the desired width
* #param text
* the text that should be that width
*/
private static void setTextSizeForWidth(Paint paint, float desiredWidth,
String text) {
// Pick a reasonably large value for the test. Larger values produce
// more accurate results, but may cause problems with hardware
// acceleration. But there are workarounds for that, too; refer to
// http://stackoverflow.com/questions/6253528/font-size-too-large-to-fit-in-cache
final float testTextSize = 48f;
// Get the bounds of the text, using our testTextSize.
paint.setTextSize(testTextSize);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
// Calculate the desired size as a proportion of our testTextSize.
float desiredTextSize = testTextSize * desiredWidth / bounds.width();
// Set the paint for that size.
paint.setTextSize(desiredTextSize);
}
Then, all you need to do is setTextSizeForWidth(paint, 400, str); (400 being the example width in the question).
For even greater efficiency, you can make the Rect a static class member, saving it from being instantiated each time. However, this may introduce concurrency issues, and would arguably hinder code clarity.
Try this:
/**
* Retrieve the maximum text size to fit in a given width.
* #param str (String): Text to check for size.
* #param maxWidth (float): Maximum allowed width.
* #return (int): The desired text size.
*/
private int determineMaxTextSize(String str, float maxWidth)
{
int size = 0;
Paint paint = new Paint();
do {
paint.setTextSize(++ size);
} while(paint.measureText(str) < maxWidth);
return size;
} //End getMaxTextSize()
Michael Scheper's solution seems nice but it didn't work for me, I needed to get the largest text size that is possible to draw in my view but this approach depends on the first text size you set, Every time you set a different size you'll get different results that can not say it is the right answer in every situation.
So I tried another way:
private float calculateMaxTextSize(String text, Paint paint, int maxWidth, int maxHeight) {
if (text == null || paint == null) return 0;
Rect bound = new Rect();
float size = 1.0f;
float step= 1.0f;
while (true) {
paint.getTextBounds(text, 0, text.length(), bound);
if (bound.width() < maxWidth && bound.height() < maxHeight) {
size += step;
paint.setTextSize(size);
} else {
return size - step;
}
}
}
It's simple, I increase the text size until the text rect bound dimensions are close enough to maxWidth and maxHeight, to decrease the loop repeats just change step to a bigger value (accuracy vs speed), Maybe it's not the best way to achieve this but It works.