I have a JTextPane with a StyledDocument and need to add an image between text that is higher than the font. This means that normally the line gets higher:
What I'm aiming for is to have the image slightly overlap, so it uses less space. In the MCVE I achieve this by returning a smaller vertical span for the IconView
, which looks like this:
So basicially it thinks the image is less high, but draws it fully anyway.
This mostly works, however there are two issues with that:
- The additional height still only gets added on the top, it might be nicer if it's evenly distributed.
- But more importantly sometimes the bottom part isn't drawn, for example when scrolling up and then down again:
Quite understandable, given that the lower part isn't really where the image is expected to be.
My question is now, is there a better (less hacky) way to prevent the image from taking up so much space? Or at least a way to fix the drawing? I already dug around the code a bit, but I'm not sure what part is responsible for deciding what is drawn or how to best change it without messing anything else up.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
public class OverlappingImage {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createGui();
});
}
private static void createGui() {
JTextPane textPane = new JTextPane();
textPane.setEditorKit(new MyEditorKit());
textPane.setEditable(false);
textPane.setPreferredSize(new Dimension(320, 200));
StyledDocument doc = textPane.getStyledDocument();
SimpleAttributeSet iconStyle = new SimpleAttributeSet();
StyleConstants.setIcon(iconStyle, createImage());
try {
doc.insertString(doc.getLength(), TEST_TEXT+"\n"+TEST_TEXT, null);
doc.insertString(doc.getLength(), "Image", iconStyle);
doc.insertString(doc.getLength(), TEST_TEXT, null);
} catch (BadLocationException ex) {
Logger.getLogger(OverlappingImage.class.getName()).log(Level.SEVERE, null, ex);
}
JFrame window = new JFrame();
window.add(new JScrollPane(textPane), BorderLayout.CENTER);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setLocationByPlatform(true);
window.setTitle("Test");
window.setVisible(true);
}
static class MyEditorKit extends StyledEditorKit {
private final ViewFactory factory;
public MyEditorKit() {
this.factory = new StyledViewFactory();
}
@Override
public ViewFactory getViewFactory() {
return factory;
}
static class StyledViewFactory implements ViewFactory {
@Override
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new MyIconView(elem);
}
}
return new LabelView(elem);
}
}
}
static class MyIconView extends IconView {
public MyIconView(Element elem) {
super(elem);
}
@Override
public float getPreferredSpan(int axis) {
if (axis == View.Y_AXIS) {
float height = super.getPreferredSpan(axis);
return height * 0.7f;
}
return super.getPreferredSpan(axis);
}
}
/**
* Creates the example image.
*/
public static ImageIcon createImage() {
BufferedImage image = new BufferedImage(28,28, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 0, 28, 28);
g.setColor(Color.BLACK);
g.drawRect(0, 0, 27, 27);
g.dispose();
return new ImageIcon(image);
}
private static final String TEST_TEXT = "Lorem ipsum dolor sit amet, "
+ "consectetur adipisici elit, sed eiusmod tempor incidunt ut "
+ "labore et dolore magna aliqua. Ut enim ad minim veniam, quis "
+ "nostrud exercitation ullamco laboris nisi ut aliquid ex ea "
+ "commodi consequat. Quis aute iure reprehenderit in voluptate "
+ "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur "
+ "sint obcaecat cupiditat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum.";
}