DPI Scaling for CSSBox

Radek Burget’s CSSBox is an (X)HTML/CSS rendering engine written in pure Java with AWT/Swing display output. While it doesn’t support JavaScript, it does support modern HTML/CSS and is also tolerant of any content it doesn’t understand. This makes CSSBox a good replacement for the standard JEditorPane which is limited to HTML 3.2 with some extensions, and simply beeps when it can’t parse something, meaning it’s useless for third-party content.

You could of course instead embed an actual web browser such as the JavaFX WebView. However, such libraries are massive – tens of megabytes – and also platform-dependent since browser engines contain native code. CSSBox with all its required libraries is only 3 MB and cross-platform. So when I started porting MIME Browser from JavaFX to Swing and discovered the limitations of JEditorPane, CSSBox turned out to be an ideal solution.

High DPI Scaling Issue

There is however one defect that only becomes apparent on high DPI screens with Java SE 9 or later. In that version AWT/Swing finally got automatic high DPI scaling, just like JavaFX or WPF. The problem? CSSBox renders to a BufferedImage for display output – and this particular class does not distinguish between layout coordinates and screen pixels, which is the basis for automatic DPI scaling.

When drawing to a high DPI screen, layout coordinates are scaled so everything is the proper size, but actual drawing is performed with screen pixel precision so everything looks nice and sharp. When drawing to a BufferedImage, layout and drawing coordinates are scaled, meaning you get a pixelated mess.

The screenshot below gives a sample at scaling factor 200% on Windows 10 – try reading the fine print in the last line. (Choose “View Image” or the like in your browser to get a full-size version that makes the defective scaling easier to see.)

Image Drawing

To my knowledge there is no easy solution for this image scaling issue. One would need to do manual DPI scaling: enlarge the image by the current DPI scaling factor, multiply all drawing coordinates accordingly, and then scale the image back down for screen output. This is quite cumbersome, as well as memory-intensive and slow.

For my purposes a much simpler solution was to rip out the BufferedImage rendering in class BrowserCanvas and instead render directly to the screen by overriding paintComponent as usual. The remaining code only needed a few supporting changes. You can download my creatively titled BrowserCanvas2 – it’s a bit too large to show here. The second screenshot shows the greatly improved rendering results.

Screen Drawing

I’ll send a bug report to the CSSBox project. My code is no general substitute for the existing BrowserCanvas because rendering to image files is one of the project’s advertised features, and my changed code can no longer do that. So the engine would need two paths: rendering to the screen by default, and rendering to an image on demand. I’d rather leave the task of doing this properly to the project maintainer.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.