我使用JEditorPane作为"橡皮图章"将HTML文本呈现为PDF。我需要文本以特定的宽度换行,并且需要应用白色"突出显示"在文本背后。因此,我在PDF渲染线程中创建一个JEditorPane,设置文本和样式表,然后将其绘制为PDF图形。但是,我在HTML编辑器工具包的内部深处发现了间歇性的NullPointerException。这可以通过此SSCCE重现:
import javax.swing.*;
import javax.swing.text.View;
import javax.swing.text.html.HTMLDocument;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* @author sbarnum
*/
public class TextMarkerUtilsTest {
public static void main(String[] args) throws Exception {
Rectangle bounds = new Rectangle(255, 255);
BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB);
Graphics2D d = image.createGraphics();
d.setClip(bounds);
for (int i=0; i<1000; i++) {
JEditorPane renderHelper = new JEditorPane("text/html", "<html><body>This is my text.</body></html>");
HTMLDocument document = (HTMLDocument) renderHelper.getDocument();
document.getStyleSheet().addRule("foo{color:black;}");
View rootView = renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d, bounds);
}
}
}
运行上面的命令会抛出以下异常,通常只需循环几次:
java.lang.NullPointerException
at sun.font.FontDesignMetrics$MetricsKey.init(FontDesignMetrics.java:199)
at sun.font.FontDesignMetrics.getMetrics(FontDesignMetrics.java:267)
at sun.swing.SwingUtilities2.getFontMetrics(SwingUtilities2.java:949)
at javax.swing.JComponent.getFontMetrics(JComponent.java:1599)
at javax.swing.text.LabelView.getFontMetrics(LabelView.java:154)
at javax.swing.text.html.InlineView.calculateLongestWordSpanUseWhitespace(InlineView.java:246)
at javax.swing.text.html.InlineView.calculateLongestWordSpan(InlineView.java:191)
at javax.swing.text.html.InlineView.getLongestWordSpan(InlineView.java:177)
at javax.swing.text.html.ParagraphView.calculateMinorAxisRequirements(ParagraphView.java:140)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:551)
at javax.swing.text.html.ParagraphView.getMinimumSpan(ParagraphView.java:261)
at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:886)
at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:129)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:551)
at javax.swing.text.html.BlockView.getMinimumSpan(BlockView.java:361)
at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:886)
at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:129)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.setSpanOnAxis(BoxView.java:326)
at javax.swing.text.BoxView.layout(BoxView.java:691)
at javax.swing.text.BoxView.setSize(BoxView.java:380)
at javax.swing.plaf.basic.BasicTextUI$RootView.setSize(BasicTextUI.java:1703)
at javax.swing.plaf.basic.BasicTextUI$RootView.paint(BasicTextUI.java:1422)
at com.prosc.msi.model.editor.TextMarkerUtilsTest$1.run(TextMarkerUtilsTest.java:40)
at java.lang.Thread.run(Thread.java:680)
一些有趣的发现:
addRule("foo{color:black;}")
的调用,它就可以了(我需要指定规则,但它似乎并不重要,如果添加了任何规则,它就会失败)问题出在javax.swing.text.GlyphPainter1.sync()
,其中javax.swing.text.GlyphView.getFont()
返回null。通过设置条件断点,我发现GlyphView
在这种情况下是javax.swing.text.html.InlineView
。在断点停止后调用getFont()
会返回一个非空字体,因此没有及时初始化某些内容。
我意识到swing组件不是线程安全的,但是我不能在后台线程中实例化JEditorPane并在后台线程中安全地操作它,只要只有一个线程正在制作调用组件?
答案 0 :(得分:1)
由于您只使用轻量级组件,headless mode可能是一种选择。您可以使用ProcessBuilder
使用here来完成GUI的EDT工作。
public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
Rectangle bounds = new Rectangle(255, 255);
BufferedImage image = new BufferedImage(
bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB);
Graphics2D d = image.createGraphics();
d.setClip(bounds);
for (int i = 0; i < 1000; i++) {
JEditorPane renderHelper = new JEditorPane(
"text/html", "<html><body>This is my text.</body></html>");
HTMLDocument document = (HTMLDocument) renderHelper.getDocument();
document.getStyleSheet().addRule("foo{color:black;}");
View rootView = renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d, bounds);
}
}
});
}
答案 1 :(得分:0)
感谢Marko建议查找Event Dispatch Thread的回调,我最终在HTMLDocument.styleChanged()
找到了一个。我的子类:
public class ThreadFriendlyHTMLDocument extends HTMLDocument {
@Override
protected void styleChanged(final Style style) {
// to fix GlyphPainter1.sync NullPointerException, we call this in the current thread, instead of the EDT
DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
this.getLength(),
DocumentEvent.EventType.CHANGE);
dde.end();
fireChangedUpdate(dde);
}
}