Why do JTextArea and TextLayout wrap words differently? - java

We have an app that draws text, but then displays a JTextArea for the user to edit the text when they click on the text. However, the wrapping between these two text-handling components differs. They use the same width, text String, and Font.
For the text-drawing, I'm using the from the Java tutorial, which I've also seen used by others in related questions here and other forums. Here's that part of the code:
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout;
AttributedString attrString = new AttributedString(myText);
AttributedCharacterIterator charIterator;
int paragraphStart;
int paragraphEnd;
LineBreakMeasurer lineMeasurer;
float breakWidth;
float drawPosX;
float drawPosY;
attrString.addAttribute(TextAttribute.FONT, myFont);
charIterator = attrString.getIterator();
paragraphStart = charIterator.getBeginIndex();
paragraphEnd = charIterator.getEndIndex();
lineMeasurer = new LineBreakMeasurer(charIterator, frc);
// Set break width to width of Component.
breakWidth = myTextWidth;
drawPosY = startY
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
textBounds = new Rectangle(startX, startY(), 0, 0);
// Get lines from until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
drawPosX = layout.isLeftToRight()
? startX() : breakWidth - layout.getAdvance();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2d, drawPosX, drawPosY);
lineBounds = new Rectangle2D.Float(drawPosX, drawPosY - layout.getAscent(), layout.getAdvance(), (layout.getAscent() + layout.getDescent() + layout.getLeading()));
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getAscent() + layout.getDescent() + layout.getLeading();
}
The JTextArea is much simpler:
JTextArea textArea = new JTextArea(myText);
textArea.setSize(myTextWidth, myTextThing.getHeight());
textArea.setOpaque(true);
textArea.setVisible(true);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setFont(myFont);
textArea.setBorder(null);
I set the border to null because I have another rectangle drawn outside the bounds of the text area with a dashed area to show where it is. Might seem silly now, but we use it to show the bounds of the text area when the user first selects the text they want to edit. At that point, the JTextArea isn't yet created. They have to click on it again to begin editing. The reason for this is that once a text area is selected, they may also drag and resize the text area, and that gets messy and more confusing if they had a live JTextArea when they started dragging and resizing.
Separately, both the drawn TextLayouts and the JTextArea appear to wrap words just fine. but when used together you can see the difference. The problem with this is that while the user is editing the text, the JTextArea is doing its thing to wrap the text. But when the user JTextArea loses focus, it is converted to the drawn text, and then the words may be wrapped differently.

Fill the text area with i or l characters. Grab a UI ruler or magnifying glass and count the size of your text area in pixels from the leftmost character of the longest line to the rightmost. Do the same with n, m, and a few other characters for a few more data points. I suspect that the text area has an invisible border of a few pixels it uses even when set to no border. If this is the case, add the same border around the TextLayout component and they should appear identical.
(Alternatively to counting pixels, you could set a background color for the text or the components, but I wouldn't necessarily trust it.)

Related

Java Swing. JTextPane. Getting start and end character seen by user [duplicate]

I have a JeditorPane in a JScrollPane. At certain points in the application, I would like to retrieve the text that is visible in the scrollPane (the text that is currently showing) and only this text. Is there a way to do this?
Thank you,
Elliott
You can use the viewport to get the view position and size.
JViewport viewport = scrollPane.getViewport();
Point startPoint = viewport.getViewPosition();
Dimension size = viewport.getExtentSize();
Point endPoint = new Point(startPoint.x + size.width, startPoint.y + size.height);
Once you know the start/end points of the viewport you can use:
int start = editorPane.viewToModel( startPoint );
int end = editorPane.viewToModel( endPoint );
Once you know the offsets of the text you want you can get the text from the component:
String text = editorPane.getText(start, end - start);
None of the code is tested.

libgdx: Label height multiple lines

I have a Label object with a text in it...
Label label = new Label(text);
..and I get the height by...
label.pack();
GlyphLayout layout = label.getGlyphLayout();
float height = layout.height;
When I add them to a table I just said
Table t = new Table();
t.add(label).width(200).height(height);
Currently I work on a chat window and each entry of the
window is a Label containing a colored name and text, like...
Note: The debug lines are drawn. Please ignore the blue line, it's from another table in the scene
Now my question: The 1-liner has a glyphlayout height of 38 and the 3-liner 139. Does someone know why there's such a big gap between the 2 entries resp. why the 3-liner height doesn't seem to be correct? No extra spacing / padding.
I would appreciate any ideas to solve this.
SOLVED:
My post was incomplete, sorry. I set the width of the label before calling...
label.setWidth(x); // where x was < chatbox width
label.pack();
So the GlyphLayout assumed a width which was smaller than the actual chatbox width. The result is of course a bigger height...
Thanks a lot to the libgdx IRC, especially Tomski :)

Is it possible to highlight a character partially?

Is it possible to achieve something like this in Swing :
Notice here A is colored partially.
I know may be its not possible with DefaultHighlighter class alone.
(Text displayed on JTextArea)
Any solutions to achieve this in Swing's alone or have to apply CSS?
EDIT:
If that is not possible with swing, any solution with the below tags ?
Something like this is possible with Java 2D but not with JTextArea. So you can create code which renders this in a frame but I don't think you'll find an easy way to add support for this to the text editors (well, you could always embed an image in a text editor but that's probably not what you want).
[EDIT] For a text display for a karaoke player, Java 2D is the way to go. The Java editor APIs will just get in your way.
Here is an example for rendering text with a gradient:
public void sayWorld(Graphics2D g2D, int x, int y, boolean shear) {
final String txt = "Hello World!";
// gradient color from blue to red
GradientPaint gp = new GradientPaint((float)x, (float)y, Color.blue,
x+100, y+20, Color.red);
g2D.setPaint(gp);
if (shear) g2D.shear(-0.5,0.0);
else g2D.shear(+0.5, 0);
g2D.drawString(txt, x, y);
FontRenderContext frc = new FontRenderContext(null,false,false);
TextLayout tl = new TextLayout(txt, font, frc);
AffineTransform textAt = new AffineTransform();
//textAt.translate(0, (float)tl.getBounds().getHeight());
textAt.translate(x,y);
//textAt.shear(-0.5,0.0);
Shape outline = tl.getOutline(textAt);
g2D.setColor(Color.yellow);
BasicStroke wideStroke = new BasicStroke(2);
g2D.setStroke(wideStroke);
g2D.draw(outline);
}
(source)
Basically, you render get the text layout from a single line of the lyrics, then convert that into a shape (= the outline of each letter). You can then use this shape as a clip area to render the gradient.
In your case, you need an abrupt gradient which is twice as wide as the longest text line that you want to render. Shift it appropriately to get the gradient change in the place of the text where you need it.

Calculating the dimensions of a rendered string of text (before rendering it)

I want to align a String drawn by a canvas perfectly in the middle. So if I have code like so
Paint p = new Paint();
Canvas c = holder.lockCanvas();
String centered_text = "Hello World";
and then set my Paint style and size
p.setTextSize(35);
p.setStyle(Style.STROKE);
Now to draw my String in the middle I would do
c.drawText(centered_text, c.getWidth()/2, c.getHeight()/2, p);
But this wouldn't actually center my text, it would put the top left corner in the center. To center it I would need to know the Strings litteral width and height in pixels not characters.
int string_width = centered_text.getWidth(); //String.getWidth() is not a real methode
int string_height = centered_text.getHeight(); //String.getHeight() is not a real methode
c.drawText(centered_text, (c.getWidth()/2)-(string_width/2), (c.getHeight()/2)-(string_height/2), p);
This would center the text but String.getWidth() and String.getHeight() are not real methodes. So my idea is to use the size of the text and the size of the String to find the width and the height. So something along te lines of:
int string_width = centered_text.length()*p.getTextSize();
int string_height = p.getTextSize();
But I feel like this is wrong because different chars are different sizes... anyone have any ideas.
Assuming you're in Java Swing, what you want is FontMetrics.
new FontMetrics(canvas.getFont()).getStringBounds(someString, canvas.getGraphics());
will return a Rectangle2D with the size of your string in it.

libGDX: Textfield with BitmapFont(true) renders the text outside the borders

I'm experimenting with TextField and having problems with it when flipping the font. My orthographic camera is set to yDown = true. With that settings, the text is flipped so I came up with a solution to set BitmapFont's flip constructor parameter to true. But then when I try the code below. The text "Hello World" is rendering outside it's ninepatch borders. Here's a screenshot of it:
TextFieldStyle tfs = new TextFieldStyle();
NinePatch np = new NinePatch(new Texture(Gdx.files.internal(ResourceConstants.IMAGE_NINEPATCH)), 8, 8, 8, 8);
tfs.font = new BitmapFont(true);
tfs.fontColor = Color.BLACK;
tfs.background = np;
TextField tf = new TextField("Hello World", tfs);
tf.x = 50;
tf.y = 90;
tf.width = 100;
tf.height = 32;
addActor(tf);
tf.pack();
The problem is in the method where draw is being called on tfs (which is where the coordinates are set). The cartesian y coordinates for font are opposite other GDX objects, I think because typesetting needs to work a certain way.
So if you call
myFont.draw(spriteBatch, "Hello World", 0, 0);
Then you would expect the message to be drawn right in the bottom left hand side. But this is wrong! The fonts themselves are drawn from the top left, so your message will be drawn on the bottom left corner of the screen, below the bottom edge. We wont even be able to see the message.
But if we change the coordinates:
myfont.draw(spriteBatch, "box2d x: " + String.format("%2.2f", x), 10, 20);
We will see the message because we've given it enough room in the negative y direction to be displayed.
Given that the misbehaving font is misbehaving in the Y direction, and rendering below where you expect it to, I suspect that the above misconception is indeed the problem.
If you're not controlling any of the drawing coordinates of the bitmapfont itself, and this is solely handled by the TextField class, and the font is always out of bounds, no matter the size of your text field, then I would suspect a bug in GDX. You might try asking the forums about that.
I had the same effect. If you add TextField, and after, for example, add CheckBox and for this widget set setScale, then you will see this effect
TextField textfield = new TextField("Text field",skin);
stage.addActor(textfield);
CheckBoxStyle checkBoxStyle = skin.get(CheckBoxStyle.class);
checkBoxStyle.font.getData().setScale(2f);
CheckBox checkbox = new CheckBox("CheckBox", checkBoxStyle);
stage.addActor(checkbox);

Categories