4

I submitted another version of this question and a sample program before: How do I get consistent rendering when scaling a JTextPane?

Recapitulating the problem: I would like to allow users to zoom into or out of a non-editable JTextPane. Running the example program submitted in the earlier question, which simply scaled the Graphics object, resulted in inconsistent spacing between runs of bold text and non-bold text.

The sample program below attempts to solve the problem by drawing the text pane to a BufferedImage at 100% and then scaling the image. This solves the problem of inconsistent spacing but the resulting text lacks crispness. Is there some combination of rendering hints (or some other change) that will result in nice crisp text?

Thanks in advance for any suggestions or comments on the feasibility of this approach.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.Box;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

public class ScaledJTextPane extends JTextPane
{
    double scale_;
    BufferedImage raster_;

    public ScaledJTextPane()
    {
        scale_ = 1.0;
        raster_ = null;
    }

    public void draw(Graphics g)
    {
        if (raster_ == null)
        {
            // Draw this text pane to a BufferedImage at 100%
            raster_ = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = raster_.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);

            paint(g2);
        }

        Graphics2D g2 = (Graphics2D) g;

        // Experiment with different rendering hints
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);

        // Scale the BufferedImage            
        g2.scale(scale_, scale_);
        g2.drawImage(raster_, 0, 0, null);
    }

    public void setScale(double scale)
    {
        scale_ = scale;
        raster_ = null;
    }

    private static void createAndShowGUI() 
    {
        // Create and set up the window.
        JFrame frame = new JFrame("ScaledJTextPane using BufferedImage");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final ScaledJTextPane scaledTextPane = new ScaledJTextPane();
        StyledDocument doc = scaledTextPane.getStyledDocument();
        Style defaultStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
        Style boldStyle = doc.addStyle("bold", defaultStyle);
        StyleConstants.setBold(boldStyle, true);

        scaledTextPane.setFont(new Font("Dialog", Font.PLAIN, 14));
        String boldText = "Four score and seven years ago ";
        String plainText = "our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.";
        try 
        {
            doc.insertString(doc.getLength(), boldText, boldStyle);
            doc.insertString(doc.getLength(), plainText, defaultStyle);
        } 
        catch (BadLocationException ble) 
        {
            System.err.println("Couldn't insert text into text pane.");
        }

        final JComboBox zoomCombo=new JComboBox(new String[] {"75%",
                "100%", "150%", "175%", "200%"});
        final JPanel panel = new JPanel()
        {
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);
                scaledTextPane.draw(g);
            }
        };
        zoomCombo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String s = (String) zoomCombo.getSelectedItem();
                s = s.substring(0, s.length() - 1);
                double scale = new Double(s).doubleValue() / 100;
                scaledTextPane.setScale(scale);
                panel.invalidate();
                panel.repaint();
            }
        });
        zoomCombo.setSelectedItem("100%");

        JPanel optionsPanel = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        c.anchor = GridBagConstraints.WEST;

        optionsPanel.add(zoomCombo, c);

        c.gridx++;
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        optionsPanel.add(Box.createHorizontalGlue(), c);

        // Add content to the window.
        scaledTextPane.setBounds(0, 0, 450, 300);
        panel.setOpaque(true);
        panel.setBackground(Color.WHITE);
        frame.getContentPane().add(panel, BorderLayout.CENTER);
        frame.getContentPane().add(optionsPanel, BorderLayout.NORTH);
        frame.setSize(900, 300);

        //Display the window.
        frame.setVisible(true);
    }

    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                createAndShowGUI();
            }
        });
    }
}
Community
  • 1
  • 1
jht
  • 125
  • 5

3 Answers3

3

may be this http://java-sl.com/Scale_In_JEditorPane.html could help.

StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • Actually, that's where I started. I've been using your ScaledEditorKit, but there have been a couple of issues that I haven't been able to resolve: (incorrect caret positioning at some widths of the text pane – jht Dec 31 '10 at 21:53
  • continuing that comment... and since upgrading to Java 6 Update 14 and later versions, inconsistent rendering). I am hoping to simplify things by only allowing editing at 100%. – jht Dec 31 '10 at 22:04
  • The problems description is too common. Unfortunately I can't say anything related. Need detailed description of the wrong behaviour. – StanislavL Jan 03 '11 at 11:15
  • I've revisited this. Your ScaledGlyphPainter seems to solve both problems. The detailed report of the first problem is http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6736241. I'll accept this solution though changing glyph painters at this point causes a serious backward capability problem because the text layout is so different. Thanks. – jht Jan 08 '11 at 12:35
  • Additionally you can try GlyphPainter based on TextLayout (see for example GlyphPainter2 SUN's class) but it is much slower. – StanislavL Jan 10 '11 at 09:26
2

Sadly, scaling to a larger size from a fixed resolution will always result in some aliasing artifact. Here's an alternative approach that scales the font used by JTextPane.

For low-level control, consider TextLayout, which includes a FontRenderContext that can manage the anti-aliasing and fractional metrics settings, as seen in this example.

alt text

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

/** @see https://stackoverflow.com/questions/4566211 */
public class ScaledJTextPane {

    private static final int SIZE = 14;
    private static final String FONT = "Dialog";

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("ScaledJTextPane using BufferedImage");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JTextPane tp = new JTextPane();
        tp.setFont(new Font(FONT, Font.PLAIN, SIZE));
        tp.setPreferredSize(new Dimension(400, 300));
        StyledDocument doc = tp.getStyledDocument();
        Style defaultStyle = StyleContext.getDefaultStyleContext()
            .getStyle(StyleContext.DEFAULT_STYLE);
        Style boldStyle = doc.addStyle("bold", defaultStyle);
        StyleConstants.setBold(boldStyle, true);
        String boldText = "Four score and seven years ago ";
        String plainText = "our fathers brought forth on this continent, "
            + "a new nation, conceived in Liberty, and dedicated to the "
            + "proposition that all men are created equal.";
        try {
            doc.insertString(doc.getLength(), boldText, boldStyle);
            doc.insertString(doc.getLength(), plainText, defaultStyle);
        } catch (BadLocationException ble) {
            ble.printStackTrace(System.err);
        }
        final JPanel panel = new JPanel();
        panel.add(tp);

        final JComboBox zoomCombo = new JComboBox(new String[]{
                "75%", "100%", "150%", "175%", "200%"});
        zoomCombo.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                String s = (String) zoomCombo.getSelectedItem();
                s = s.substring(0, s.length() - 1);
                double scale = new Double(s).doubleValue() / 100;
                int size = (int) (SIZE * scale);
                tp.setFont(new Font(FONT, Font.PLAIN, size));
            }
        });
        zoomCombo.setSelectedItem("100%");
        JPanel optionsPanel = new JPanel();
        optionsPanel.add(zoomCombo);
        panel.setBackground(Color.WHITE);
        frame.add(panel, BorderLayout.CENTER);
        frame.add(optionsPanel, BorderLayout.NORTH);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • This is an interesting approach and might be something I could use. In the real world the text pane could contain more than one font size which would mean iterating through the document elements. Also, I don't want to modify the text pane, just display it scaled, so I would have to maintain a copy of the text pane at the base size. – jht Dec 31 '10 at 21:39
0

I would like to allow users to zoom into or out of a non-editable JTextPane.

Since the text pane is non-editable, maybe you can create an image of the text pane by using the Screen Image class. Then you can draw the image on a panel using the approriate scaling factor.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thank you for the suggestion, but this seems to boil down to the approach that I tried in the code above -- namely to create a BufferedImage of the text pane and then drawing the image using the appropriate scaling factor. The problem that I encountered with this approach is that the resulting text was much less crisp than that created by simply scaling the Graphics object and painting the text pane directly. – jht Dec 30 '10 at 21:44