Search code examples
javaswingurijtextpane

file:// URI scheme in JTextPane with HTML content is not working


I'm making an image cache in the folder of my application. I made code for downloading images and checking for their existence.

However, I cannot use them in src attributes for some reason.

I have read these two posts:

jEditorPane handling local images

Display images using JEditorPane

However, in the first question the OP said that he used another method for setting image source, that is not suitable in my case.

And the second article assumes that file:// scheme should be working out of the box without any tricks. But somehow it's not working in my case.

A tried replacing forward slashes with backslashes, replacing spaces with %20 and leaving them as they are, tried file:// and file:/// prefix - nothing is working. I can read my files using ImageIO, but JTextPane HTML renderer does not find them, so I have just error placeholders on screen.

The example of the path to an image:

file:///D:/My%20Documents/NetBeansProjects/XMessenger/cache/http%3A%2F%2Fpopov654.pp.ru%2Fcopybox%2Fphoto.jpg

Here is the code:

private int last_width = 0;
private HashMap<String, Image> imgCache = new HashMap<String, Image>();

private void createTestMessageContent(final JComponent parent) {

    String html = "Some <i>text</i><br>And more,<br>And more...<br>And more...<br>And more...<br>And more...<br>And more...<br>" + 
            "<img src=\"http://popov654.pp.ru/copybox/photo.jpg\" width=\"390\" height=\"260\">" +
            "<img src=\"http://popov654.pp.ru/copybox/DSCN2155.jpg\" width=\"390\" height=\"260\">" +
            "<img src=\"http://popov654.pp.ru/copybox/DSCN2157.jpg\" width=\"390\" height=\"260\">";
    html = "<div style=\"padding: 0px; font-size: 14px; font-family: Tahoma\">" + html + "</div>";

    final String orig_html = html;

    Matcher m = Pattern.compile("((<img src=\"[^\"]+\")( width=\"\\d+\")?( height=\"\\d+\")?([^>]*>)\\s*)+").matcher(orig_html);
    while (m.find()) {
        String str = m.group();
        Matcher m2 = Pattern.compile("src=\"([^\"]+)\"").matcher(str);
        while (m2.find()) {
            File f = new File("cache");
            if (!f.exists()) {
                f.mkdir();
            }
            String src = m2.group(1);
            String cached_name = src.replaceAll("/", "%2F").replaceAll(":", "%3A");
            File img = new File("cache/" + cached_name);
            System.err.println(img.getAbsolutePath());
            if (!img.exists()) {
                InputStream in = null;
                BufferedInputStream b_in = null;
                FileOutputStream fileOutputStream = null;
                try {
                    in = new URL(src).openStream();
                    b_in = new BufferedInputStream(in);
                    fileOutputStream = new FileOutputStream("cache/" + cached_name);
                    byte dataBuffer[] = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
                        fileOutputStream.write(dataBuffer, 0, bytesRead);
                    }
                    //Files.copy(in, Paths.get("cache/" + cached_name), StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        in.close();
                        b_in.close();
                        fileOutputStream.close();
                    } catch (IOException ex) {}
                }
            } else {
                html = html.replaceFirst("src=\"" + src + "\"", "src=\"file:///" + img.getAbsolutePath().replaceAll("\\\\", "/").replaceAll(" ", "%20") + "\"");
            }
        }
    }

    final String cached_html = html;

    html = html.replaceAll("(<img src=\"[^\"]+\")( width=\"\\d+\")?( height=\"\\d+\")?([^>]*>)", "<a href=\"#\" style=\"display: inline-block; margin-right: 10px\">$1 width=\"390\" height=\"260\" hspace=\"2\" vspace=\"8\" border=\"0\"$4</a>");
    System.out.println(html);

    msgContent.setText(html);
    addHyperlinkListener(msgContent);


    //msgContent.setMinimumSize(new Dimension(msgContent.getWidth(), msgContent.getPreferredSize().height + 20));
    //setMinimumSize(new Dimension(getWidth(), msgContent.getPreferredSize().height + 160));
    setPreferredSize(new Dimension(getWidth(), msgContent.getPreferredSize().height + 148));
    
    initImages();

    updateSize();

    content2.addComponentListener(new java.awt.event.ComponentListener() {

        @Override
        public void componentResized(ComponentEvent e) {
            int new_width = ((JPanel)e.getSource()).getWidth();
            if (new_width != last_width) updateImages(cached_html);
            last_width = new_width;
        }

        @Override
        public void componentMoved(ComponentEvent e) {}

        @Override
        public void componentShown(ComponentEvent e) {}

        @Override
        public void componentHidden(ComponentEvent e) {}

    });
}

private void initImages() {
    HTMLDocument doc = (HTMLDocument) msgContent.getStyledDocument();
    Element[] imgs = getElementsByTagName(HTML.Tag.IMG, doc);
    for (Element img: imgs) {
        Element a = img.getParentElement();
        try {
            String src = (String) img.getAttributes().getAttribute(HTML.Attribute.SRC);
            System.out.println("src=" + src);
            System.out.println();

            Image image = null;
            if (!src.startsWith("http://") && !src.startsWith("https://")) {
                String path = src;
                if (src.startsWith("file:///")) {
                    path = path.substring(8).replaceAll("%20", " ");
                }
                image = ImageIO.read(new File(path));
            } else {
                image = ImageIO.read(new URL(src));
            }
            imgCache.put(src, image);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

private void updateImages(String orig_html) {
    int width = XMessengerApp.getMainWindow().getMessagePaneWidth() - 108;

    double default_ratio = 16f / 9;

    int img_width = (int) (width * 0.65);
    int img_height = (int) ((double)img_width / default_ratio);
    int margin = (int) (width * 0.005);

    String html = orig_html;
    Matcher m = Pattern.compile("((<img src=\"[^\"]+\")( width=\"\\d+\")?( height=\"\\d+\")?([^>]*>)\\s*)+").matcher(orig_html);
    while (m.find()) {
        String str = m.group();
        String str_new = str;
        String[] img_strs = str.split(">\\s*<");
        if (img_strs.length > 1) {
            System.err.println("Image series found of " + img_strs.length + " images");
        }
        if (img_strs.length > 1) {
            img_width = (int)((width - margin * img_strs.length) / img_strs.length * 0.95);
            img_height = (int)((double)img_width / default_ratio);
        }
        for (int i = 0; i < img_strs.length; i++) {
            if (i > 0) img_strs[i] = "<" + img_strs[i];
            if (i < img_strs.length-1) img_strs[i] = img_strs[i] + ">";

            Matcher m2 = Pattern.compile("src=\"([^\"]+)\"").matcher(img_strs[i]);
            m2.find();
            String src = m2.group(1);

            double ratio = default_ratio;
            if (imgCache.containsKey(src)) {
                Image img = imgCache.get(src);
                ratio = (double) img.getWidth(null) / img.getHeight(null);
                //System.out.println("Aspect ratio: " + ratio);
            }
            if (img_strs.length == 1) {
                img_height = (int)((double)img_width / ratio);
            } else {
                img_width = (int)((double)img_height * ratio);
            }
            //System.out.println("Src: " + src);

            String replace = img_strs[i].replaceAll("(<img src=\"[^\"]+\")( width=\"\\d+\")?( height=\"\\d+\")?([^>]*>)", "<a href=\"#\" style=\"display: inline-block; margin-right: " + margin + "px\">$1 width=\"" + img_width + "\" height=\"" + img_height + "\" hspace=\"" + margin + "\"vspace=\"8\" border=\"0\"$4</a>");
            str_new = str_new.replaceFirst(img_strs[i], replace);
        }
        if (img_strs.length > 1) {
            str_new = "<div style=\"float: left; margin-left: -10px\">" + str_new + "</div>";
        }
        html = html.replaceFirst(str, str_new);
            
    }


    msgContent.setText(html);

}

private void updateSize() {
    try {
        Dimension d = msgContent.getPreferredSize();
        Rectangle r = msgContent.modelToView(msgContent.getDocument().getLength());
        d.height = r.y + r.height;
        msgContent.setPreferredSize(d);
        Dimension d2 = content2.getPreferredSize();
        d2.height = r.y + r.height + 200;
        content2.setPreferredSize(d2);
        JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
        topFrame.getContentPane().validate();
    } catch (Exception ex) {}
}

Solution

  • Found the problem. It seems that I should escape % symbols in my filenames, replacing it with %25, but only if I use that path inside a URL string. When I'm using File() to work with that file it is not needed.