I am trying to print using the TextLayout class. When I print some mathematical symbols, I get odd behavior.
The image below shows the results from printing the strings "First + 2" and "First \u222A 2" to a PDF file. When the 'union' symbol (\u222A) is printed, it looks like characters following the symbol overwrite characters before the symbol.
Here is the test code that prints a string that does not contain \u222A and another string that does:
public class PrintTest {
public static void main(String[] args) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(new Printable() {
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (pageIndex != 0) return NO_SUCH_PAGE;
Graphics2D g2 = (Graphics2D) g;
Font font = new Font("SansSerif", Font.PLAIN, 10);
// Draw string with + char
TextLayout layout = new TextLayout("First + 2", font, g2
.getFontRenderContext());
layout.draw(g2, 40, 80);
// Draw string with union character
layout = new TextLayout("First \u222A 2", font, g2
.getFontRenderContext());
layout.draw(g2, 40, 100);
return PAGE_EXISTS;
}
});
if (pj.printDialog()) {
try {
pj.print();
} catch (PrinterException e) {
System.out.println(e.getMessage());
}
}
}
}
I am running java 1.7.0_17 on Windows 7 & 8, and have tested this on several printers, including printing to PDF. All tests show the same problem. I get the same results with 'union', 'intersection' and 'exclusion' symbols.
I am really stuck here. Any help would be greatly appreciated.
UPDATE: The problem seems to occur when using logical fonts. I tested with all of the installed logical fonts ('Dialog', 'DialogInput', 'Monospaced, 'SansSerif' and 'Serif') and got the same problem. When I tested with physical fonts that support these symbols ('Lucida Sans' and 'Arial Unicode MS') I got the desired output.
This resolves the immediate issue for me since I'm not tied to using logical fonts.
Actually, you are printing Unicode characters all the time; the title of your post suggests you are printing something other than Unicode. All the alphabetic characters that you print correctly are encoded in Unicode, since that is how Java encodes strings.
So you are trying to print the "Union" symbol, and it isn't working. What evidence do you have that it is supported on the printer you are printing on? It is quite possibly not supported on any printer you've tried, hence unpredictable behavior. It is the most obvious thing that could cause this.
Related
There is not a lot to explain. Just see the MCVE/image below:
public class FontExample extends JFrame {
private static final Font FONT = new Font("Calibri", Font.PLAIN, 14);
public FontExample() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setFont(FONT);
withoutHtml.setBorder(BorderFactory.createLineBorder(Color.red));
add(withoutHtml);
JLabel withHtml = new JLabel("<html><body style='vertical-align:top;'>hello stackoverflow");
withHtml.setBorder(BorderFactory.createLineBorder(Color.green));
withHtml.setFont(FONT);
add(withHtml);
setLocationByPlatform(true);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
//Make sure Calibri font is installed
if (!"Calibri".equals(FONT.getFamily())) {
System.err.println("Font calibri is not installed.");
System.exit(1);
}
new FontExample().setVisible(true);
});
}
}
The green one is with the <html> tag. Is there a way to fix it? And by fix, I mean to make it like the left one, without this stupid space?
It does not seem to happen with any other font (I tested 2-3 more). I am on Java 8 with Windows 7 and Windows 10.
I tried to add padding at bottom:
JLabel withHtml = new JLabel("<html><body style='padding-bottom:5px'>hello stackoverflow");
and as expected what I get is this:
which a) will screw the alignment of other components in the same container (bad for UI purposes) and b) I will have to hard code a lot of values since 5 since to be the proper for font size 14. But for other font size, it needs another value.
#Andrew Thomson in comments said to use the HTML format for all JLabels. But then, if they are next to another text-based component like a JTextField, I get this:
which obviously, is bad too.
UPDATE
Also, I tried to download Calibri font (among with variations like "Calibri Light", etc) somewhere from the web and install it as described in this question. I do not know if that "Overrides" the existing one, but I had the same result.
A line of text consists of 3 parts:
The ascent
The descent
The leading
To see more clearly, I used Calibri with size 50. The label without HTML is:
In HTML mode, things are different. The HTML renderer puts the leading first (for some reason):
This gives the unpleasant result you have observed.
Now you will ask "But why do I see that effect only with Calibri?" In fact the effect exists with all fonts, but it's usually much smaller, so you don't notice it.
Here is a program that outputs the metrics for some common Windows fonts:
import java.awt.*;
import javax.swing.JLabel;
public class FontInfo
{
static void info(String family, int size)
{
Font font = new Font(family, Font.PLAIN, size);
if(!font.getFamily().equals(family))
throw new RuntimeException("Font not available: "+family);
FontMetrics fm = new JLabel().getFontMetrics(font);
System.out.printf("%-16s %2d %2d %2d\n", family, fm.getAscent(), fm.getDescent(), fm.getLeading());
}
public static void main(String[] args)
{
String[] fonts = {"Arial", "Calibri", "Courier New", "Segoe UI", "Tahoma", "Times New Roman", "Verdana"};
System.out.printf("%-16s %s\n", "", " A D L");
for(String f : fonts)
info(f, 50);
}
}
For size 50, the results are:
A D L
Arial 46 11 2
Calibri 38 13 11
Courier New 42 15 0
Segoe UI 54 13 0
Tahoma 50 11 0
Times New Roman 45 11 2
Verdana 51 11 0
As you can see, the leading for Calibri is huge compared to the other fonts.
For size 14, the results are:
A D L
Arial 13 3 1
Calibri 11 4 3
Courier New 12 5 0
Segoe UI 16 4 0
Tahoma 14 3 0
Times New Roman 13 3 1
Verdana 15 3 0
The leading for Calibri is still 3 pixels. Other fonts have 0 or 1, which means the effect for them is invisible or very small.
It doesn't seem possible to change the behavior of the HTML renderer. However, if the goal is to align the baselines of adjacent components, then it is possible. The FlowLayout you have used has an alignOnBaseline property. If you enable it, it does align the components correctly:
UPDATE 1
Here's a JFixedLabel class that gives the same result, whether it contains HTML or plain text. It translates the Graphics by the leading value when in HTML mode:
import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.plaf.basic.BasicHTML;
public class JFixedLabel extends JLabel
{
public JFixedLabel(String text)
{
super(text);
}
#Override
protected void paintComponent(Graphics g)
{
int dy;
if(getClientProperty(BasicHTML.propertyKey)!=null)
dy = getFontMetrics(getFont()).getLeading();
else
dy = 0;
g.translate(0, -dy);
super.paintComponent(g);
g.translate(0, dy);
}
}
Result:
UPDATE 2
The previous solution had an issue with icons, so here's a new one that handles both text and icons. Here we don't extend JLabel, instead we define a new UI class:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.metal.MetalLabelUI;
public class FixedLabelUI extends MetalLabelUI
{
#Override
protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text, Icon icon,
Rectangle viewR, Rectangle iconR, Rectangle textR)
{
String res = super.layoutCL(label, fontMetrics, text, icon, viewR, iconR, textR);
if(label.getClientProperty(BasicHTML.propertyKey)!=null)
textR.y -= fontMetrics.getLeading();
return res;
}
}
To assign the UI to a label, do like this:
JLabel label = new JLabel();
label.setUI(new FixedLabelUI());
Olivier's answer suggests to use flowLayout.setAlignOnBaseline(true); but it will not work in another Layoutmanagers, e.g GridLayout. However, it helped me a lot to find the exact solution I was looking for. Even if it is a messy/hacky one.
Here it is:
If you System.out.println(label.getFontMetrics(label.getFont())), you will see that the actual class of the FontMetrics is FontDesignMetrics. Luckily for us, the getters for the values ascent, descent and leading rely on the fields without some crazy calculations. Luckily for us vol.2, These font metrics are the same (equals) for the same font. That means, we have a single FontDesignMetrics instance of for each Font style-size combination (and obviously its family).
With other words:
private static final Font FONT = new Font("Calibri", Font.PLAIN, 50);
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setFont(FONT);
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setFont(FONT);
FontMetrics withHtmlFontMetrics = withHtml.getFontMetrics(withHtml.getFont());
FontMetrics withoutHtmlFontMetrics = withoutHtml.getFontMetrics(withoutHtml.getFont());
boolean equals = withHtmlFontMetrics.equals(withoutHtmlFontMetrics);
System.out.println(equals);
It prints true even if the getFontMetrics was called in different labels. If you withHtml.setFont(FONT.deriveFont(Font.BOLD)); you will see that it prints false. Because the font is different, we have different font metrics instance.
The fix
(Disclaimer: Desperate times call for desperate measures)
As I already mentioned, it's some sort of hacky and it relies on reflection. With reflection we can manipulate these 3 values. Something like:
FontMetrics fontMetrics = label.getFontMetrics(label.getFont());
Field descentField = fontMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(fontMetrics, 0);
But you are going to either hard code values for each font size/style, or you can do what I did.
What I did is to copy these values from other font's FontMetrics. It looks that in case of Calibri font, Tahoma is the one.
First, create the method that change the values in the fields, taken from Tahoma font metrics:
private static void copyTahomaFontMetricsTo(JComponent component) {
try {
FontMetrics calibriMetrics = component.getFontMetrics(component.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", component.getFont().getStyle(), component.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
Now, call it by: copyTahomaFontMetricsTo(withHtml); without caring if its the withHtml label or the withoutHtml, since they both have the same font.
The result (font size in frame title):
Even with other text-based components next to it:
As you see, it is works! Plus the layout alignment is not screwed.
It looks perfect, but it's not.
Again, as mentioned earlier, for each font (combination of family, size and style), there is one instance of FontMetrics. Changing one of these label's font to Font.BOLD will stop us from getting perfect alignment. Probably a one (or two) pixels miss. Plus we will have to copyTahomaFontMetricsTo for the Bold as well:
copyTahomaFontMetricsTo(withoutBoldFont);
copyTahomaFontMetricsTo(withBoldFont);
and the result (again font size on frame's title):
Look closer:
There is one pixel difference. But I guess I will take it since this is way (way) better than Swing's/Windows default Calibri-HTML behavior:
The complete example:
public class FontExample extends JFrame {
private static final Font FONT = new Font("Calibri", Font.PLAIN, 20);
public FontExample() {
super("Font: " + FONT.getSize());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setBorder(BorderFactory.createLineBorder(Color.GREEN));
withoutHtml.setFont(FONT.deriveFont(Font.BOLD));
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setBorder(BorderFactory.createLineBorder(Color.RED));
withHtml.setFont(FONT);
copyTahomaFontMetricsTo(withoutHtml);
copyTahomaFontMetricsTo(withHtml);
add(withHtml);
setLocationByPlatform(true);
pack();
}
private static void copyTahomaFontMetricsTo(JLabel label) {
try {
FontMetrics calibriMetrics = label.getFontMetrics(label.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", label.getFont().getStyle(), label.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new FontExample().setVisible(true);
});
}
}
Two ways you can probably handle this, add
html {
margin:0;
}
or add padding to both bits of text. :)
Of course you can try
<html style="margin:0;">
<body style='vertical-align:text-bottom;' worked for me, but if I'm misunderstanding your question, you can find other values at https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
I'm writing an application in Java that retrieves the cover art of books. Most of the images that I try retrieving are displayed just fine, but periodically, I'll run into one that doesn't display and I can't for the life of me figure out why. Maybe someone can help me. Here is the relevant code:
private BufferedImage cover;
try {
cover = ImageIO.read(new URL(coverArt.getImageURLs().get(0)));
} catch (IOException exception) {
System.out.println("error");
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//System.out.println(buttonPanel.getHeight());
if (LeftPanel.getCollectionTable().getSelectedRow() >= 0) {
g.drawImage(ComicBookCollection.getComicBookCollection().get(LeftPanel.getCollectionTable().getSelectedRow() + positionAdjust).getCover(),
12, 80, getWidth() - 25, getHeight() - 130, null);
} else {
repaint();
}
}
There is a list of strings of image urls that is compiled before this. I know that the list is being compiled correctly. For some reason, it is only for specific random titles. If I put Superman 2 in there, it doesn't come up, but if I put Superman 1 in there, it does. I've tried using the url of the problematic images in other programs and they display just fine. Maybe someone can figure out what is gong on.
The code looks fine, but it's non-standard so there may be some gotcha somewhere.
Try using standard Swing components for this. I.e. have the right panel be a JLabel, and make it show the image by wrapping it in an ImageIcon object and calling setIcon on the label.
This is where I called the program to read the SpriteSheet, which was in another class.
private SpriteSheet spriteSheet = new SpriteSheet("/sprite_sheet.png");
This is where I tried to read the pixel colors from the sprite sheet which was colored with Black, Dark grey, Light grey, and White. This is meant to print out color details for the first row of pixels.
public SpriteSheet(String path) {
BufferedImage image = null;
try {
image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path));
} catch (IOException e) {
e.printStackTrace();
}
if(image==null){
return;
}
this.path = path;
this.width = image.getWidth();
this.height = image.getHeight();
pixels = image.getRGB(0, 0, width, height, null, 0, width);
for(int i = 0; i<pixels.length;i++){
pixels[i] = (pixels[i] & 0xff)/64;
}
for(int i=0; i<8;i++){
System.out.println(pixels[i]);
}
When I run this it does not print the numbers like I coded. How could I fix this? And how does the reading of colors work?
If you aren't getting any output then either your println lines aren't getting reached or (far less likely) there's something wrong with your console configuration that is hiding output. We will assume, hopefully correctly, that all methods called here always either return or throw (i.e. nothing is hanging). Presuming the former, the only opportunity I see for that to happen is if the image fails to load and you return.
Since you print the stack trace of any exceptions but do not report seeing a stack trace, that means that ImageIO.read() must be returning null. For that to happen, according to the documentation:
The InputStream is wrapped in an ImageInputStream. If no registered ImageReader claims to be able to read the resulting stream, null is returned.
I do know that PNG is supported generally, but perhaps your specific flavor of PNG is not (e.g. a compressed, 4-color palette or something; I don't know what the limitations of ImageIO are here). I suggest you try saving the image in a different format, or testing with a more baseline PNG format (e.g. uncompressed 24-bit, no transparency, no interlacing).
If my analysis here is correct, the moral of this story is: Improve your error handling. Don't silently fail; especially when performing tasks that are critical to the correct functioning of your program.
If I am not correct, I suggest you either drop some debugging printouts in your method or step through in a debugger to see why those println lines aren't getting executed.
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).
maybe someone can give hand of help and tell how to create and print form
like this:
using java.
Also, it should be filled with needed information.
java.awt.print - Java 2D printing, since JDK 1.2
javax.print - aka the Java Print Service (JPS) API, since JDK 1.4
from http://java.sun.com/javase/technologies/desktop/printing/
I think you need a bit of googling - it looks like a very trivial task.
If you are using Swing, the follow the procedure below:
For A4 setting:
Use a JFrame of approx. 750 px. X 960 px.
In the Window use JLabels, JTextFields and JTextAreas to Design the template.
Also do add a print button anywhere on the window (to initiate the print command).
Now when all designing is complete, in the code window of the button action event, simply
add:
<Button Name>.setVisible(false);
<PanelName>.print();
First one will hide the Button, second will actually present you with a print dialog.
Additionally, use Netbeans IDE to save time in designing. It is a great time saver in the designing, compiling and testing grounds.
Please revert back for any doubts, Hope the information is helpful.
If you need to do it in a web application, the printing should be done in javascript. But you may render the page using Java. http://shyarmal.blogspot.com/2011/08/printing-example-with-java-ee.html
If you are doing it using swing: http://shyarmal.blogspot.com/2011/08/printing-with-jeditorpane.html
A little late, but I'll leave this here for reference:
//pertinent code only
import java.awt.print
public void FilePrintClicked(){
PrinterJob job = PrinterJob.getPrinterJob();
PageFormat format = job.defaultPage();
format.setOrientation(PageFormat.LANDSCAPE);
job.setPrintable(this, format);
try{
if(job.printDialog()) job.print();
}
catch(Exception e){e.printStackTrace();}
}
public int print(Graphics g, PageFormat format, int pagenum) {
if (pagenum > 0){
return Printable.NO_SUCH_PAGE;
}
g.translate((int)format.getImageableX(), (int)format.getImageableY());
float pageWidth = (float)format.getImageableWidth();
float pageHeight = (float)format.getImageableHeight();
float imageHeight = (float)this.getHeight();
float imageWidth = (float)this.getWidth();
float scaleFactor = Math.min((float)pageWidth/(float)imageWidth, (float)pageHeight/(float)imageHeight);
int scaledWidth = (int)(((float)imageWidth)*scaleFactor);
int scaledHeight = (int)(((float)imageHeight)*scaleFactor);
BufferedImage canvas = new BufferedImage( this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D gg = canvas.createGraphics();
this.paint( gg );
Image img = canvas ;
g.drawImage(img, 0, 0, scaledWidth, scaledHeight, null );
return Printable.PAGE_EXISTS;
}
Note: Your class needs to implement Printable
It's a little dirty, but it's rather old code from when I was learning Java and I didn't double-check it as I posted it here, but it's working in my application so.....