Jeditorpane with Inline Image

JEditorPane with inline image

You need to add a protocol handler for "data:" so an URL/URLConnection can be opened for it. Alternatively you could create some protocol handler "resource:" for class path resources.

You need a package data with a class Handler (fixed name convention!). This will be the factory class for "data:" return an URLConnection. We will create DataConnection for that.

Installing a protocol handler can be done via System.setProperty. Here I provided Handler.install(); to do that in a generic way.

package test1.data;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

public class Handler extends URLStreamHandler {

@Override
protected URLConnection openConnection(URL u) throws IOException {
return new DataConnection(u);
}

public static void install() {
String pkgName = Handler.class.getPackage().getName();
String pkg = pkgName.substring(0, pkgName.lastIndexOf('.'));

String protocolHandlers = System.getProperty("java.protocol.handler.pkgs", "");
if (!protocolHandlers.contains(pkg)) {
if (!protocolHandlers.isEmpty()) {
protocolHandlers += "|";
}
protocolHandlers += pkg;
System.setProperty("java.protocol.handler.pkgs", protocolHandlers);
}
}
}

The URLConnection gives an InputStream to the bytes:

package test1.data;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import javax.xml.bind.DatatypeConverter;

public class DataConnection extends URLConnection {

public DataConnection(URL u) {
super(u);
}

@Override
public void connect() throws IOException {
connected = true;
}

@Override
public InputStream getInputStream() throws IOException {
String data = url.toString();
data = data.replaceFirst("^.*;base64,", "");
System.out.println("Data: " + data);
byte[] bytes = DatatypeConverter.parseBase64Binary(data);
return new ByteArrayInputStream(bytes);
}

}

The clever thing here is to use Base64 decoding of DatatypeConverter in standard Java SE.


P.S.

Nowadays one would use Base64.getEncoder().encode(...).

Set Inline Text and Image in a JEditorPane

  • javax/swing/text/html/ImageView.java
/**
* Update any cached values that come from attributes.
*/
protected void setPropertiesFromAttributes() {
StyleSheet sheet = getStyleSheet();
this.attr = sheet.getViewAttributes(this);

//...

AttributeSet attr = getElement().getAttributes();

// Alignment.
// PENDING: This needs to be changed to support the CSS versions
// when conversion from ALIGN to VERTICAL_ALIGN is complete.
Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);

vAlign = 1.0f;
if (alignment != null) {
alignment = alignment.toString();
if ("top".equals(alignment)) {
vAlign = 0f;
}
else if ("middle".equals(alignment)) {
vAlign = .5f;
}
}
  • By referring to the above code, specifying "align" instead of "valign" does not seem to work well.

    • styleSheet.addRule("img {padding-top: 10; align: middle; valign: middle; vertical-align: middle; }");

The following is an example of overriding the ImageView#getAlignment(...) method.

screenshot

16x16.png

import java.awt.*;
import java.net.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;

public class ImgBaselineTest {
private JComponent makeUI() {
JEditorPane editor2 = makeEditorPane(new ImgBaselineHTMLEditorKit());
JEditorPane editor1 = makeEditorPane(new HTMLEditorKit());
JPanel p = new JPanel(new GridLayout(2, 1));
p.add(new JScrollPane(editor1));
p.add(new JScrollPane(editor2));
return p;
}
private JEditorPane makeEditorPane(HTMLEditorKit kit) {
JEditorPane jep = new JEditorPane();
jep.setContentType("text/html");
jep.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
// HTMLEditorKit kit = new HTMLEditorKit();
// HTMLEditorKit kit = new ImgBaselineHTMLEditorKit();
jep.setEditorKit(kit);
StyleSheet styleSheet = kit.getStyleSheet();
styleSheet.addRule("span {color:orange; font-family:times; margin: 4px; display: inline; line-height: 20px;}");
styleSheet.addRule("img {padding-top: 10; align: middle; valign: middle; vertical-align: middle; }");

//URL url = ImgBaselineTest.class.getResource("16x16.png");
String url = "https://i.stack.imgur.com/MOQoc.png";
// jep.setText("<span>Hello computer! <img src='" + url + "' width='20' height='20' valign='middle'></span>");
jep.setText("<span>Hello computer! <img src='" + url + "''></span>");
return jep;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new ImgBaselineTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}

class ImgBaselineHTMLEditorKit extends HTMLEditorKit {
@Override public ViewFactory getViewFactory() {
return new HTMLEditorKit.HTMLFactory() {
@Override public View create(Element elem) {
View view = super.create(elem);
if (view instanceof LabelView) {
System.out.println(view.getAlignment(View.Y_AXIS));
}
AttributeSet attrs = elem.getAttributes();
Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
Object o = elementName != null ? null : attrs.getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag kind = (HTML.Tag) o;
if (kind == HTML.Tag.IMG) {
return new ImageView(elem) {
@Override public float getAlignment(int axis) {
switch (axis) {
case View.Y_AXIS:
return .8125f; // magic number...
default:
return super.getAlignment(axis);
}
}
};
}
}
return view;
}
};
}
}

JEditorPane with inline image from jar file

I got it finally working.

public class ResourceConnection extends URLConnection {
protected ResourceConnection(URL url) {
super(url);
}

@Override
public void connect() throws IOException {
connected = true;
}

@Override
public InputStream getInputStream() throws IOException {
return ResourceConnection.class.getResource(url.getFile()).openConnection().getInputStream();
}
}

Thanks to Andrew Thompson for the idea to work with streams.

JEditorPane Content Type for HTML Embedded Base64 Images

You can try something like below :

    String imgsrc = this.getClass().getClassLoader()
.getResource("your_package_name/image.png").toString();

editor_pane.setContentType("text/html");
editor_pane.setEditable(false);
editor_pane.setText("<h3>Image Title</h3><img src='" + imgsrc + "' alt='img' name='img_name' width='100' height='100' /><br />");

UPDATE :

    String imgURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAACXBIWXMAAFxGAABcRgEUlENBAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCBIsgCBIsgiAIEiyCIAgSLIIgSLAIgiBIsAiCIEiwCIIgwSIIgiDBIgiCIMEiCIIEiyAIggSLIAiCBIsgCBIsgiAIEiyCIAgSLIIgSLAIgiBIsAiCIMGiLCAIggSLIAiCBIsgCBIsgiAIEiyCIAgSLIIgSLAIgiBIsAiCIEiwCIIgwSIIgiDBIgiCIMEiCIIEiyAIggSLIAiCBIsgCBIsgiAIEiyCIAgSLIIgSLAIgiBIsAiCIEiwCIIgwSIIgiDBIgiCIMEiCIIEiyAIggSLIAiCBIsgCBIsgiAIEiyCIEiwCIIgSLAIgiBIsAiCIMEiCIIgwSIIgiDBIgiCBIsgCIIEiyAIggSLIAgSLIIgCBIsgiAIEiyCIGoz/z8A3OLuKa2cevQAAAAASUVORK5CYII=";

String encodedImg = imgURL.split(",")[1];
byte[] decodedImg = Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8));

Path destinationFile = Paths.get("C:\\your_path\\", "myImage.jpg");
try {
Path path = Files.write(destinationFile, decodedImg);
editor_pane.setContentType("text/html");
editor_pane.setEditable(false);
editor_pane.setText("<h3>Image Title</h3><img src='" + path.toAbsolutePath().toUri() + "' alt='img_alt' width='150' height='150' /><br />");
} catch (IOException ex) {
ex.printStackTrace();
}

UPDATE 2 :

You have to create custom toolkit for the JEditorPane like below :

public class CustomToolKit extends HTMLEditorKit {

private static HTMLFactory factory = null;

@Override
public ViewFactory getViewFactory() {
if (factory == null) {
factory = new HTMLFactory() {

@Override
public View create(Element elem) {
AttributeSet attrs = elem.getAttributes();
Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag kind = (HTML.Tag) o;
if (kind == HTML.Tag.IMG) {
return new BASE64ImageView(elem);
}
}
return super.create(elem);
}
};
}
return factory;
}

}

Then you have to override the getImageURL() of javax.swing.text.html.ImageView to support Base64 encoded images:

public class BASE64ImageView extends ImageView {

private URL url;

public BASE64ImageView(Element elmnt) {
super(elmnt);
populateImage();
}

private void populateImage() {
Dictionary<URL, Image> cache = (Dictionary<URL, Image>) getDocument()
.getProperty("imageCache");
if (cache == null) {
cache = new Hashtable<>();
getDocument().putProperty("imageCache", cache);
}

URL src = getImageURL();
cache.put(src, loadImage());

}

private Image loadImage() {
String b64 = getBASE64Image();
BufferedImage newImage = null;
ByteArrayInputStream bais = null;
try {
bais = new ByteArrayInputStream(
Base64.getDecoder().decode(b64.getBytes()));
newImage = ImageIO.read(bais);
} catch (Throwable ex) {
ex.printStackTrace();
}
return newImage;
}


Related Topics



Leave a reply



Submit