Changing the colour of text in drawstring() - java

I'm trying to add emphasis to one work in a string im drawing using swing.
I was advised to use HTML with the following code:
Graphics2D g2 = (Graphics2D) g;
g.drawString("this is something I want people to <p color="#00FF00">NOTICE</p>", x, y);
I tried this but had no luck... it just outputs the HTML
Can anyone point me in the right direction?

How does this compile: g.drawString("this is something I want people to <p color="#00FF00">NOTICE</p>", x, y); as ' " ' is a special character we must escape it with \
You cast to Graphics2D but dont use it (not relevant to problem but can cause anomalies).
It should be:
Graphics2D g2 = (Graphics2D) g;
g2.drawString("this is something I want people to <p color=\"#00FF00\">NOTICE</p>", x, y);
to add colour simply call setColor(Color c) on Graphics object:
g2.setColor(Color.GREEN);
However this will set the entire String to be drawn green, if you want only parts to be drawn green use JLabel for HTML support (up to HTML3.2):
JLabel label = new JLabel("<html>this is something I want people to <p color=\"#00FF00\">NOTICE</p></html>");
full example:
NB As you can see notice is on its own line thats because of the paragraph tag rather use font tag to get it on a single line like so:
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Test {
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("<html>this is something I want people to <p color=\"#00FF00\">NOTICE</p></html>");
// JLabel label = new JLabel("<html>this is something I want people to <font color=\"#00FF00\">NOTICE</font></html>");//will be shown on single line
frame.add(label);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}

Use Graphics.setColor() to change the color of anything you do. Or use a JLabel to set the color on.

Use a JLabel for styled text. See LabelRenderTest for how it can be drawn to an image & used in paint.
Using Graphics/AWT methods
The string implies NOTICE should be green, but the rest default (black). We would need to call drawString(String) twice with the colors of the two parts of the string, offsetting the latter string by the width of the first. To get the width, see things like FontMetrics or a GlyphVector. This answer uses a GlyphVector to get an outline of the letters.

If you're just creating a simple label with emphasis on a word, you can just assign the HTML straight onto the JLabel like this...
JLabel label = new JLabel("<html>this is something I want people to <p color='#00FF00'>NOTICE</p>");
As long as you have the <html> piece at the beginning of the String for a JLabel, it will use the HTML renderer to paint it.
As pointed out by #AndrewThompson, however, the <p> will force the colored text on to a new line, so perhaps <span> would be more appropriate...
JLabel label = new JLabel("<html>this is something I want people to <span style='color:#00FF00;'>NOTICE</span>");

you can use g.setColor(Color.BLUE) before g.drawString(). (For example Color.BLUE)

You could add g.setColor(Color.Chosen Color);and then write the string out with g.drawString()
perhaps

Related

Multi-colored JLabel

I would like a different way to create a multi-colored JLabel.
(Multi-colored = parts of the text in different foreground-colors)
The only solution I found so far (and which I currently use), is setting the text in html. But I'm having problems with that...
When the LayoutManager decides that the JLabel should be narrowed, with a plain-text in a JLabel, the text gets kind of cropped, and "..." is added.
(e.g.: "My Long Text" -> becomes: "My Long T...")
With html inside a JLabel, the text is wrapped somewhere on a space-character, leaving the rest of outside the drawable area, and invisible as the JLabel's height is unchanged.
(e.g.: "My Long Text" -> becomes: "My Long")
In my case the JLabel is rendered in a JTable, which gets resized by the user, not to mention in different screen resolutions.
I tried adding a "nowrap" attribute or a ""-tag to the html, but it looks like this is ignored.
Leaving me -I think- with one solution: painting the label myself.
Or not?
Any suggestions?
Examples?
Thank you.
Here's a very simple example:
Try to resize this panel horizontally, and see what happens with the text inside both JLabel's...
(there's no indication for the user, that the text of the second JLabel is not the complete content)
-> In the example, the JLabel's height changes, but when rendered inside the framework's JTable, the height of the rows doesn't change and I don't want it to change. Without the use of HTML it doesn't change the height either...
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class MultiJLabel
extends JFrame
{
public MultiJLabel()
{
super("Multi-colored JLabel test");
JPanel pnl = new JPanel();
pnl.setLayout(new BorderLayout());
pnl.add(new JLabel("This is a test of My Long Text"), BorderLayout.NORTH);
pnl.add(new JLabel("<html>This is a test of <font color='#ffbebe'>My Long Text</font></html>"), BorderLayout.SOUTH);
this.getContentPane().add(pnl);
this.pack();
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args)
{
new MultiJLabel();
}
}
Here's a picture of the original problem, where our users are not aware that the client's Order Number is not what the grid is showing, because this column has HTML-formatted text to show multi-colors.
Thank you all for your comments, but I was impatient and created my own JLabel.
I know it may be a poor programmed version, but it works for me...
You can test it by altering the above example with:
JMultiColorLabel lbl = new JMultiColorLabel("This is a test of My Long Text");
lbl.setColors(new int[]{10,15}, new Color[]{Color.RED,Color.BLUE});
lbl.setPreferredSize(new Dimension(200,20));
pnl.add(lbl, BorderLayout.SOUTH);
And use this class:
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.util.HashMap;
import javax.swing.JLabel;
public class JMultiColorLabel
extends JLabel
{
private static final String STRING_OVERFLOW = "...";
private HashMap<Integer, Color> extraColors = new HashMap<Integer, Color>();
public JMultiColorLabel(String text)
{
super(text);
}
public void setColors(int[] indices, Color[] colors)
{
for (int i = 0; i < indices.length; i++)
this.extraColors.put(indices[i], colors[i]);
}
protected void paintComponent(Graphics g)
{
// Get text-contents of Label
String text = this.getText();
// No text in the JLabel? -> No risk: super
if (text == null || text.length() == 0)
{
super.paintComponent(g);
return;
}
// Content Array of characters to paint
char[] chars = text.toCharArray();
// Draw nice and smooth
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// Draw background
if (this.isOpaque())
{
g2d.setColor(this.getBackground());
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
}
// FontMetrics to calculate widths and height
FontMetrics fm = g2d.getFontMetrics();
// Available space
Insets ins = this.getInsets();
int maxSpace = this.getWidth()-(ins.left+ins.right);
boolean overflow = (fm.stringWidth(text) > maxSpace);
// Starting offset
int offset = ins.left+1;
// The start Color is the default
g2d.setColor(this.getForeground());
// Loop over characters
for (int i = 0; i < chars.length; i++)
{
// Switch Color?
if (this.extraColors.containsKey(i))
g2d.setColor(this.extraColors.get(i));
// Check if we still have enough room for this character
if (overflow && offset >= (maxSpace-fm.stringWidth(STRING_OVERFLOW)))
{ // Draw overflow and stop painting
g2d.drawString(STRING_OVERFLOW, offset, (fm.getHeight()+ins.top));
return;
}
else // We have the space -> Draw the character
g2d.drawString(String.valueOf(chars[i]), offset, (fm.getHeight()+ins.top));
// Move cursor to the next horizontal position
offset += fm.charWidth(chars[i]);
}
}
}
To prevent line wrapping when using html-text in JLabels, wrap the text in nobr (no-break) tags:
new JLabel("<html><nobr>This is a test of <font color='#ffbebe'>My Long Text</font></nobr></html>")
When using the nobr-tags, the line will not be wrapped, but it won't be truncated as well. So, there won't be any ellipsis (...) at the end of the shown text, but it will just cut off.
The missing ... might actually be of advantage in a table as there is no additional width lost by the ellipsis, and thus more content shown. But to the user it might be less obvious that there is more content without them.

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.

Unordered List Bullets Look Pixelated in JEditorPane

I'm using an HTML unordered list to display some text within a JEditorPane. A sample of the HTML looks like this:
<ul><li>Cautious</li>
<li>Curious</li>
</ul>
This works all well and good, but strangely the bullets generated by <li> don't look to be the same quality as the text it is next to. (Running Mac OS 10.7.5 if it matters). The circular bullets look blocky and pixelated:
Normal zoom:
Zoomed in:
As especially evident when it is zoomed in, this is just a block of pixels that aren't symmetrical and lack any form of anti-aliasing, which makes for a less than convincing circular bullet point. Compared to the text next to it, it looks "off" to a discerning eye, even at normal zoom.
Is there any way that I can fix this?
EDIT: Using the • character (option + 8 on a Mac) creates a smaller bullet point in the text that does not look pixelated. I could of course insert this character manually rather than using <ul><li>, but I'd like to use an HTML unordered list if I can.
Use rendering hints to turn on anti-aliasing property KEY_ANTIALIASING.
You set some rendering hints globally, but that does not seem to work for KEY_ANTIALIASING, so you need to override the paint() method of JEditorPane.
public class HTMLTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String html = "<ul><li>Cautious</li><li>Curious</li></ul>";
JEditorPane pane = new JEditorPane("text/html", html) {
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paint(g2d);
g2d.dispose();
}
};
pane.setVisible(true);
JOptionPane.showMessageDialog(null, pane);
}
});
}
}
You need to modify the bullet points. Create an image of a better-looking bullet point, and then add CSS to the editor pane to make it use your new bullet image.
JEditorPane pane = new JEditorPane();
String u = "C:/path/to/bullet.png";
HTMLEditorKit htmlEditorKit = (HTMLEditorKit) pane.getEditorKit();
StyleSheet styleSheet = htmlEditorKit.getStyleSheet();
styleSheet.addRule(String.format("ul{list-style-image:url(%s);margin:0px 20px;", u));

Formatting with java: Mixing text and pictures

I have a certain text, that i want to annotate for the user. Unfortunately i don't even see where to begin. My algorithm gives as output a range on a string. What i am going for is something like this:
I would need two ways to marker the Characters ( Blue line, red line), maybe also to invert the character (Give a character a different background), or make them fat. What is especially difficult is aligning the Pictures (here designated by two black dots) with the characters. Since the characters should be in Courier New, i could know where to put the offset, but i cannot seem to do it.
Lastly i would have to apply a break after X characters and begin a new line, just like in the picture. I have not found any example how to approach this with java yet. With python i could use ImageDraw, but i am out of the water with java.
is it possible to show this in a canvas on the screen and export this as svg or pdf? I dont know any libraries that could do this. So i would be happy to receive some suggestions/examples also along this way.
The key is to deal with the FontMetrics API. The best you can do is looking at this reference doc.
Here is a sample code demonstrating this usage. It draws red and blue lines around an "Hello world" text according a range of chars.
The text is inside a JLabel, but you can adapt the paint method on any component (but you will have to call graphics.drawChars to paint the text.)
(the code is not very nice, but it demonstrates the usage of FontMetrics)
package com.example.swing;
import javax.swing.*;
import java.awt.*;
public class DemoFontMetrics {
public static void main(String[] args){
JFrame frame = new JFrame();
DecoratedLabel label = new DecoratedLabel("hello world !",new int[]{2,4}, new int[]{6,9});
JPanel textContainer = new JPanel(new FlowLayout());
textContainer.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
textContainer.add(label);
frame.getContentPane().add(textContainer);
frame.pack();
frame.setVisible(true);
}
private static class DecoratedLabel extends JLabel{
int startBlue;
int endBlue;
int startRed;
int endRed;
private DecoratedLabel(String text, int[] blueRange, int[] redRange) {
super(text);
startBlue = blueRange[0];
endBlue = blueRange[1];
startRed = redRange[0];
endRed = redRange[1];
}
#Override
public void paint(Graphics g) {
super.paint(g); //draw text
//set line with : 3
Stroke stroke = new BasicStroke(3f);
((Graphics2D)g).setStroke(stroke);
FontMetrics fm = g.getFontMetrics();
int h = fm.getHeight();
//compute blue line coordonate
fm.stringWidth(getText().substring(0,startBlue));
int x1 = fm.stringWidth(getText().substring(0, startBlue));
int x2 = fm.stringWidth(getText().substring(0, endBlue));
g.setColor(Color.BLUE);
g.drawLine(x1,0,x2,0);// draw blue line
//compute red line coordonates
int x3 = fm.stringWidth(getText().substring(0,startRed));
int x4 = fm.stringWidth(getText().substring(0, endRed));
g.setColor(Color.RED);
g.drawLine(x3,h-1,x4,h-1); // draw redline
}
}
}
If the text is displayed via JTextPane you can conveniently define a custom HighlightPainter, that draws lines above or under the text.
Then you can add highlights to the text pane programmatically by calling:
textPane.getHighlighter().addHighlight(startPos, endPos,
myLineHighlightPainter);
Images may as well easily be added to the pane, by:
textPane.setIcon(myImageIcon);
You can create svg directly for example: http://xmlgraphics.apache.org/batik/ it's an xml based vector graphics format.
EDIT: You can display svg in java, you can create pdf's with it from java. You can publish it in the web (simply as svg).

How can I repaint efficiently when using big custom component in Swing?

I have made a custom component (derived from JComponent) which represents
a draggable Bezier-curve.
(looks like a hanging cable, someone might know it
from Bender or Cubase)
My problem is: The curve may become really long,
let's say from top left to bottom right corners of the desktop.
This makes Swing's repaint functionality inefficient:
The area of the curve is perhaps few hundred pixels, but the area of
the component (being mostly 'transparent') is millions of pixels big.
My subjection impression is:
The longer the curve, the more flicker I get when dragging it.
I hope I made myself clear about the problem.
Perhaps it would help when I somehow could choose by myself, which regions
of the component needs repainting at all.
EDIT:
Such a mess! I'm profiling the application using Netbeans, which helps to
find inefficient code normally, but this Swing framework is making hundreds
of nested calls! I just can't figure out, what is slow and why.
By the way, disabling super.paint(...) or super.paintComponent(...) doesn't help.
Check out Filthy Rich Clients by Chet Haase and Romain Guy. They address these very optimizations among others along the way to producing responsive and graphically impressive UI.
Doing all of your bezier mathematics on the paint thread everytime the component is refreshed is (as you've gathered) a bad idea. Does your curve change often? If not then why not paint it to a BufferedImage as and when it changes, and change your paint() code to simply draw the buffered image to the component instead.
class CurveComponent extends JComponent {
private BufferedImage image;
#Override
public void paintComponent( Graphics g ) {
if ( image == null ) {
return;
}
g.drawImage( image, 0, 0, this );
}
private void updateCurve() {
image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
Graphics g = image.getGraphics();
// draw the curve onto image using g.
g.dispose();
}
}
Only call updateCurve() when you need to and all that expensive mathematics won't be needlessly repeated. Painting should be pretty responsive, even for a fullscreen window. drawImage() will be doing a straightforward memory copy and should be lightning fast.
Try writing a tiny test app, which consists of nothing except what you need to reproduce this problem. This will make profiling easier. Then post that app here, so we can take a look at possible solutions.
I found your question interesting so I wrote a test app myself. This draws a Bezier curve which is continually resized as you drag. I created a gradient background to ensure this works well with a nasty background. I get good performance and low flicker, although I use top-notch machine.
It pays to read "Filthy Rich Clients" to learn all the tricks of writing custom Swing components that perform really well.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
public class CustomComponent extends JComponent {
private Point2D start = new Point2D.Double(0, 0);
private Point2D end = new Point2D.Double(300, 200);
private CustomComponent() {
this.setOpaque(true);
final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
setEnd(e.getPoint());
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
}
public void setStart(Point2D start) {
this.start = start;
repaint();
}
public void setEnd(Point2D end) {
this.end = end;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw gradient background
final int width = getWidth();
final int height = getHeight();
g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
// draw Bezier curve
final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
g2.setColor(Color.BLACK);
g2.draw(shape);
g2.drawString("Click and drag to test for flickering", 100, 20);
}
public static void main(String[] args) {
final CustomComponent component = new CustomComponent();
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
component.setPreferredSize(size);
final JFrame frame = new JFrame();
frame.add(component);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Some things to note:
only overwrite paintComponent(Graphics g), not the other paintXXX() methods
set custom component to opaque if possible
only use repaint() to request repainting. Never directly order a repaint directly in your code. This lets Swing handle it well.
There is no efficient way to create lots of small clip rectangles for a diagonal structure which leaves you with two strategies to avoid flickering:
Double buffering. This needs an enormous amount of memory but the memory copy is very fast (it usually happens in the time the "electron beam" goes back from lower right to upper left ... if there was still a beam in your LCD).
Don't call super.paint() (which draws or "erases" the background) and draw the curve a second time with the background color to erase it.
For more details, see this document.
[EDIT] If fillRect() wasn't abstract, you could set a break point :) Set a break point in paint(), check who calls it and whether the background got cleared at that time. It should be since rendering would be completely wrong. Then set break points further up in the call chain.
You can redraw a smaller portion of the screen using repaint(Rectangle r)
http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)
Then you mention flicker. Since you are using swing, which uses double buffering your flickering must be coming from something else. Are you clearing the screen in paintComponent(...)? I.e. call to fillRect(...)? Don't do that, it's not needed (IIRC).
Which method do yo use to paint your curve? paint or paintComponent?
My solution was a partial re-design:
Now I don't represent each "cable"-element by a component.
Now, cables are just dummy objects (with no involved JComponent).
The repaint takes place "globally", on the content pane of the parent JFrame.
Now it's efficient, and flickers less.
just use getVisibleRect(); inside paintComponent(Graphics g) to get the area you actually need to redraw

Categories