Swing's BasicHTML.createHTMLView metrics usage - java

I'm doing some graphics processing and HTML is a perfect choice for styling the displayed content. I'm trying to reuse swing's built in html support, and it works perfectly if I hard code the height passed to View.paint, but I can't figure out how to determine how tall the bounds of the rendered content would be at runtime given a specific width.
Graphics2D g = ...
JLabel label = new JLabel("blah blah blah...");
View view = BasicHTML.createView(label, label.getText());
int minHeight = .... // Calculation magic goes here
Rectangle htmlSize = new Rectangle(0, 0, 50, minHeight);
g.setClip(htmlSize);
view.paint(g, htmlSize);
If I ask the JLabel direction with getPreferredSize() it doesn't consider wrapping at all. If I try using a JEditorPane it returns a larger, but fixed size rectangle.
Thank you.

The height can't be calculated until the width is known. When dealing with Swing components I think you need to do something like:
component.setSize(100, 1);
Dimension size = component.getPreferredSize();
Or maybe you can use the concepts present in this posting:

I have had success with this:
var view = BasicHTML.createHTMLView(new JLabel(), "<html>some html</html>");
System.out.println(view.getPreferredSpan(View.X_AXIS));
System.out.println(view.getPreferredSpan(View.Y_AXIS));

Related

How to decrease font size to 0.0001f when using drawString()?

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.

LibGdx Label text wrap bug?

I use a Label with text wrap enabled. In this case, the text wraps wrong. The text wraps into 3 lines, when it should be only 2 and the Label size only reflects the 2 lines. I debugged through the code and found the reason for this to be in the Label.layout() method. Not sure if this is a bug or if I am doing something wrong.
In the code below you can see that the text is set twice to the GlyphLayout. The first time it wraps correct, the second time we use the reduced width and it wraps into more lines as before. I think the second time we set the text into the GlyphLayout, the same width should be used.
public class Label extends Widget {
public void layout () {
...
float width = getWidth(), height = getHeight();
...
GlyphLayout layout = this.layout;
float textWidth, textHeight;
if (wrap || text.indexOf("\n") != -1) {
// Set the text into the GlyphLayout. The text is wrapped correctly here
layout.setText(font, text, 0, text.length, Color.WHITE, width, lineAlign, wrap, ellipsis);
textWidth = layout.width;
textHeight = layout.height;
...
} else {
textWidth = width;
textHeight = font.getData().capHeight;
}
...
// Set the text again into the GlyphLayout. This time with the width that we got when we set it the first time
// This time the text is wrapped wrong as it uses less width as it should
layout.setText(font, text, 0, text.length, Color.WHITE, textWidth, lineAlign, wrap, ellipsis);
...
}
}
Make sure to invalidate() the label and then pack() the label so it calculates any new preferred sizes; this may or may not fix the problem.
Looking at the source code of Label then you can see that the last invocation of layout.setText() is always invoked regardless of text wrapping or not.
The previous layout.setText() invocation is used to set the textWidth and textHeight for the later call where those values are actually used.
If wrapping is on or there is a newline character then the width and height is set to that of the label and if it is off then the width is set to the actual text width and the height set to the font data height.
From the above, another problem that may be causing this to happen is if you have scaled the font and/or label. The scaling factor is not being applied from within Label.layout() which may cause the label size to be that of 2 lines whilst the actual text is 3 lines as the width doesn't allow for overflow with wrapping set to on.
If all else fails, I would ensure that your font files are correct and that there are not any characters or data in your text that may cause a new line to occur.
I would also suggest to use another font of the same glyph width and height and see if the problem persists. If it does not then at least you know it is a problem relating to the font and not the label.
Hope this helped you.
This issue has been solved in libgdx 1.9.12

Faulty Mouse Point Detection?

Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...
This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.
Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}
Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!

Measure the width of an AttributedString in J2ME

I'm writing code against the Java Personal Basis Profile in J2ME. I need to measure the width of an AttributedString in pixels.
In Java SE, I'd get an AttributedCharacterIterator from my AttributedString and pass it to FontMetrics#getStringBounds, but in J2ME PBP, FontMetrics doesn't have a getStringBounds method, or any other method that accepts a CharacterIterator.
What do I do?
I struggled really hard with this. I needed to resize a panel to the width of an AttributedString. My solution is:
double GetWidthOfAttributedString(Graphics2D graphics2D, AttributedString attributedString) {
AttributedCharacterIterator characterIterator = attributedString.getIterator();
FontRenderContext fontRenderContext = graphics2D.getFontRenderContext();
LineBreakMeasurer lbm = new LineBreakMeasurer(characterIterator, fontRenderContext);
TextLayout textLayout = lbm.nextLayout(Integer.MAX_VALUE);
return textLayout.getBounds().getWidth();
}
It uses the LineBreakMeasurer to find a TextLayout for the string, and then simply checks the with of the TextLayout. (The wrapping width is set to Integer.MAX_VALUE, so texts wider than that will be cut off).
You can find the width of the text in pixels.
String text = "Hello world";
int widthOfText = fontObject.charsWidth(text.toCharArray(), 0, text.length());
Now, you will have the width of text in pixels in the variable widthOfText;

Java: Am I responsible for only painting the visible region of (JScrollPane) Viewport View?

I'm using the Java Tutorials example of how to use a JScrollPane (with row/column headers). The example is using a subclass of JLabel to display an image in the Viewport View. I used the sample code for displaying the row/column headers (Rule.java example code) and was perplexed at the bizarre results. I finally removed the call to getClipBounds() (apparently used to determine what region of the row/column header is visible to paint only that region) and painted the entire header, and the problem was resolved. That means that I'm now drawing the entire area (in both the row/column headers and the main Viewport). That strikes me as non-optimal.
Can anyone explain why the Java Tutorials example works properly (other than the source is not the same as that being executed in the example)?
Is it correct for me to be painting the entire pane even though it is only partially visible?
How can I determine what region of the overall object is visible in the Viewport (for row/column headers and the main Viewport) so I can just paint that region?
UPDATE:
I still don't know why the example works, but I've found that if I use JComponent.getVisibleRect() instead of Graphics.getClipBounds() things seem to work as expected. Not sure if this is the correct use of this method.
Look at this code below. I was just painting visible part.
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle view = new Rectangle();
if (getParent() instanceof JViewport) {
JViewport vp = (JViewport) getParent();
view = vp.getViewRect();
} else {
view = new Rectangle(0, 0, getWidth(), getHeight());
}
g2d.setColor(getBackground());
g2d.fillRect((int) view.getX(), (int) view.getY(), (int) view.getWidth(), (int) view.getHeight());
g2d.setColor(Color.YELLOW);
double x = view.getX();
double y = view.getY();
double w = view.getWidth();
double h = view.getHeight();
// draw Strings
for (StringShape ss : stringList) {
Rectangle sb = ss.getRectangle(g2d.getFontMetrics(ss.getFont()));
if (containShape(view, sb)) {
g2d.setFont(ss.getFont());
g2d.setColor(ss.getColor());
g2d.drawString(ss.getString(), (int) sb.getX(), (int) sb.getY());
}
}
}
JComponent.getVisibleRect() was the trick. Clearly I misunderstand the meaning/use of getClipBounds().

Categories

Resources