October 20, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Scaling Features in the JEditorPane Component

  • February 20, 2004
  • By Stanislav Lapitsky
  • Send Email »
  • More Articles »

If we do not use an integer scale factor, we get some distortions during caret navigation. It happens because of rounding errors in a default glyph painter. For example:

Suppose we have a "123" text string. Each character takes 9 pixels and 1 pixel between the characters. The string width, then, is 9+1+9+1+9=29. If we set the scale factor to 0.5, the width of each character will be 5 pixels and still 1 pixel between characters. Thus, the scaled string width is 5+1+5+1+5=17, but the caret position is 29*0.5=15.

The GlyphPainter1 class is based on integer calculations and generates rounding errors because of our float zoom factor. The class has private access and we can't change it. But, there is one trick that allows us to use a float-based painter. Such a painter, GlyphPainter2, is used when the text component should reflect bidirectional text. We set an appropriate property of our document to enforce GlyphPainter2 using the following:

scaledTextPane.getDocument().putProperty("i18n", Boolean.TRUE);

There is one more problem. When we select text in our JEditorPane with the mouse, the highlighter the uses original coordinates instead of the scaled ones. As a result, some content might remain unselected. It's only a visual effect, in fact. If we move a window on top of our component, the selection is restored. To provide correct behavior in all cases, we override the repaint() method of our JEditorPane.

public void repaint(int x, int y, int width, int height) {
       super.repaint(0,0,getWidth(),getHeight());
}

After these changes, our text editor supports zooming. If we change the zoom factor property of the document and generate a DocumentChange event, our editor will show us new scaled content.

The last step is setting our ScaledEditorKit to the JEditorPane.

Appendix

Here is the full source code of the zooming example.

/**
 * @author Stanislav Lapitsky
 * @version 1.0
 */

import java.awt.*;
import java.awt.geom.*;

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

public class ScaledTextPane extends JEditorPane {

  JComboBox zoomCombo=new JComboBox(new String[] {"50%","75%",
                                                  "100%","200%",
                                                  "250%"});
  public static void main(String[] args) {
    JFrame frame=new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    ScaledTextPane scaledTextPane=new ScaledTextPane();
    scaledTextPane.setEditorKit(new ScaledEditorKit());
    scaledTextPane.getDocument().putProperty("i18n", Boolean.TRUE);
    scaledTextPane.getDocument().putProperty("ZOOM_FACTOR",
                                             new Double(2.5));
    JScrollPane scroll=new JScrollPane(scaledTextPane);
    frame.getContentPane().add(scroll);
    frame.getContentPane().add(scaledTextPane.zoomCombo,
                               BorderLayout.NORTH);

    frame.setSize(200,200);
    frame.show();
  }

  public ScaledTextPane() {
    super();
    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.this.getDocument().putProperty("ZOOM_FACTOR",
                                          new Double(scale));
        try {
          ScaledTextPane.this.getDocument().insertString(0, "",
                                            null);    //refresh
        }
        catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    });
    zoomCombo.setSelectedItem("250%");
  }

  public void repaint(int x, int y, int width, int height) {
    super.repaint(0,0,getWidth(),getHeight());
  }

}

class ScaledEditorKit extends StyledEditorKit {
  public ViewFactory getViewFactory() {
    return new StyledViewFactory();
  }
  class StyledViewFactory implements ViewFactory {

    public View create(Element elem) {
      String kind = elem.getName();
      if (kind != null) {
        if (kind.equals(AbstractDocument.ContentElementName)) {
          return new LabelView(elem);
        } else if (kind.equals(AbstractDocument.
                               ParagraphElementName)) {
          return new ParagraphView(elem);
        } else if (kind.equals(AbstractDocument.
                               SectionElementName)) {
          return new ScaledView(elem, View.Y_AXIS);
        } else if (kind.equals(StyleConstants.
                               ComponentElementName)) {
          return new ComponentView(elem);
        } else if (kind.equals(StyleConstants.IconElementName)) {
          return new IconView(elem);
        }
      }

      // default to text display
      return new LabelView(elem);
    }

  }
}

//-----------------------------------------------------------------
class ScaledView extends BoxView{
  public ScaledView(Element elem, int axis) {
    super(elem,axis);
  }
  public double getZoomFactor() {
      Double scale=(Double)getDocument().getProperty("ZOOM_FACTOR");
      if (scale!=null) {
          return scale.doubleValue();
    }

    return 1;
  }

  public void paint(Graphics g, Shape allocation) {
    Graphics2D g2d = (Graphics2D)g;
    g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                         RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    double zoomFactor = getZoomFactor();
    AffineTransform old=g2d.getTransform();
    g2d.scale(zoomFactor, zoomFactor);
    super.paint(g2d, allocation);
    g2d.setTransform(old);
  }

  public float getMinimumSpan(int axis) {
    float f = super.getMinimumSpan(axis);
    f *= getZoomFactor();
    return f;
  }

  public float getMaximumSpan(int axis) {
    float f = super.getMaximumSpan(axis);
    f *= getZoomFactor();
    return f;
  }

  public float getPreferredSpan(int axis) {
    float f = super.getPreferredSpan(axis);
    f *= getZoomFactor();
    return f;
  }

  protected void layout(int width, int height) {
    super.layout(new Double(width / getZoomFactor()).intValue(),
                            new Double(height *
                            getZoomFactor()).intValue());
  }

  public Shape modelToView(int pos, Shape a, Position.Bias b)
         throws BadLocationException {
    double zoomFactor = getZoomFactor();
    Rectangle alloc;
    alloc = a.getBounds();
    Shape s = super.modelToView(pos, alloc, b);
    alloc = s.getBounds();
    alloc.x*=zoomFactor;
    alloc.y*=zoomFactor;
    alloc.width*=zoomFactor;
    alloc.height*=zoomFactor;

    return alloc;
  }


  public int viewToModel(float x, float y, Shape a,
                         Position.Bias[] bias) {
    double zoomFactor = getZoomFactor();
    Rectangle alloc = a.getBounds();
    x/=zoomFactor;
    y/=zoomFactor;
    alloc.x/=zoomFactor;
    alloc.y/=zoomFactor;
    alloc.width/=zoomFactor;
    alloc.height/=zoomFactor;

    return super.viewToModel(x, y, alloc, bias);
  }


}

About the Author

Stanislav Lapitsky is an offshore software developer and consultant with more than 7 years of programming experience. His area of knowledge includes java based technologies and RDBMS.



Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel