javahtmlpaintjcomponentlobo-cobra

Java/Swing offscreen rendering (Cobra HTMLPanel -> BufferedImage) Problem: Component doesn't finish redrawing first


I'm trying to render the contents of a the Java/Swing Cobra HTML renderer to an offscreen BufferedImage, for use elsewhere in my app:

 slideViewPanel.setDocument(document, rendererContext);
 BufferedImage test = new BufferedImage(300,300,BufferedImage.TYPE_INT_RGB);
 Graphics g = test.getGraphics();
 slideViewPanel.paint(g);

The resulting image in g shows a partially rendered page -- sometimes the contents of the HTMLFrame before the new document was set; sometimes a half-rendered version of the new document. I gather this is because Cobra's setDocument method just schedules the document for re-rendering, but I'm stepping through in the debugger and I don't see a second thread to do re-rendering. Anyone have any insight into what might be happening here?


Solution

  • When the page has been parsed, you need to wait until all images are loaded before laying out the component.

    Intercept all image loading to make sure all images are completely loaded before laying out the component. I did this by overiding HtmlDocumentImpl.loadImage. (Had to subclass DocumentBuilderImpl and overriding createDocument to make it work.) I used a semaphore to wait until the result of a image.getWItdh is available.

    I had to set up a timer around the parsing, as some scripts may loop and never return. I have no idea if there is a better way of solving this.

    Regarding the layout, there is a a deal of cargo cult and experimentation that lead to the snipped below , so maybe you can try to remove some things.

                htmlPanel.setVisible(true); 
        htmlPanel.setPreferredWidth(DEFAULT_PAGE_WIDTH);
        logger.info("Calculating preferred size");
    
        // Get the preferred heigth for the current width.
        Dimension psvz = htmlPanel.getPreferredSize();
        Dimension min = htmlPanel.getMinimumSize();
    
        logger.info("prf :" + psvz);
        logger.info("min :" + min);
    
        // Enlarge to the minimum width (with a limit)
        int width = Math.min(MAX_PAGE_WIDTH, Math.max(DEFAULT_PAGE_WIDTH,
                psvz.width));
        int height = psvz.height;
    
        logger.info("width :" + width);
        logger.info("heigth :" + height);
    
        htmlPanel.setSize(width, height);
    
                // actually, htmlPanel is a subclass, and this method exposes validateTree. It may work without it.
        htmlPanel.forceValidateTree(); 
    
        htmlPanel.doLayout();
    
        setImageSize(width);
        logger.info("actual size:" + htmlPanel.getSize());
    

    I couldn't figure out what a JFrame does with the HtmlPanel so that it paints its children properly. I had to paint using the children component; Ie either the htmlPanel.getBlockRenderable() or the frameset.

    Images are painted asychronous, (and the paints might abort), so before the BufferedImage can be used, all the image paints must be completed.

    I used a delegating graphics2d object overriding all the image paint methods to wait until the painting is completed.

    After that, i found that the html might benefit from a re-layout as all images sizes are known, so i invalidate the component and do the layout and painting again (or actually, I just call the code twice...)

    After that, the BufferedImage can be used. Maybe there is a simpler way.