1 /*
   2  * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 /*
  33  * This source code is provided to illustrate the usage of a given feature
  34  * or technique and has been deliberately simplified. Additional steps
  35  * required for a production-quality application, such as security checks,
  36  * input validation and proper error handling, might not be present in
  37  * this sample code.
  38  */
  39 
  40 
  41 
  42 import java.awt.BorderLayout;
  43 import java.awt.Color;
  44 import java.awt.Cursor;
  45 import java.awt.Dimension;
  46 import java.awt.Font;
  47 import java.awt.FontMetrics;
  48 import java.awt.Graphics;
  49 import java.awt.Graphics2D;
  50 import java.awt.GraphicsConfiguration;
  51 import java.awt.GraphicsEnvironment;
  52 import java.awt.Point;
  53 import java.awt.Rectangle;
  54 import java.awt.RenderingHints;
  55 import java.awt.Toolkit;
  56 import java.awt.event.AdjustmentEvent;
  57 import java.awt.event.AdjustmentListener;
  58 import java.awt.event.ComponentAdapter;
  59 import java.awt.event.ComponentEvent;
  60 import java.awt.event.MouseEvent;
  61 import java.awt.event.MouseListener;
  62 import java.awt.event.MouseMotionListener;
  63 import java.awt.font.FontRenderContext;
  64 import java.awt.font.GlyphVector;
  65 import java.awt.font.LineBreakMeasurer;
  66 import java.awt.font.TextLayout;
  67 import java.awt.geom.AffineTransform;
  68 import java.awt.geom.NoninvertibleTransformException;
  69 import java.awt.geom.Rectangle2D;
  70 import java.awt.image.BufferedImage;
  71 import java.awt.print.PageFormat;
  72 import java.awt.print.Printable;
  73 import java.awt.print.PrinterJob;
  74 import java.io.BufferedOutputStream;
  75 import java.io.FileOutputStream;
  76 import java.text.AttributedString;
  77 import java.util.EnumSet;
  78 import java.util.Vector;
  79 
  80 import javax.imageio.*;
  81 import javax.swing.*;
  82 
  83 import static java.awt.RenderingHints.*;
  84 
  85 /**
  86  * FontPanel.java
  87  *
  88  * @author Shinsuke Fukuda
  89  * @author Ankit Patel [Conversion to Swing - 01/07/30]
  90  */
  91 
  92 /// This panel is combination of the text drawing area of Font2DTest
  93 /// and the custom controlled scroll bar
  94 
  95 public final class FontPanel extends JPanel implements AdjustmentListener {
  96 
  97     /// Drawing Option Constants
  98     private final String[] STYLES =
  99       { "plain", "bold", "italic", "bold italic" };
 100 
 101     private final int NONE = 0;
 102     private final int SCALE = 1;
 103     private final int SHEAR = 2;
 104     private final int ROTATE = 3;
 105     private final String[] TRANSFORMS =
 106       { "with no transforms", "with scaling", "with Shearing", "with rotation" };
 107 
 108     private final int DRAW_STRING = 0;
 109     private final int DRAW_CHARS = 1;
 110     private final int DRAW_BYTES = 2;
 111     private final int DRAW_GLYPHV = 3;
 112     private final int TL_DRAW = 4;
 113     private final int GV_OUTLINE = 5;
 114     private final int TL_OUTLINE = 6;
 115     private final String[] METHODS = {
 116         "drawString", "drawChars", "drawBytes", "drawGlyphVector",
 117         "TextLayout.draw", "GlyphVector.getOutline", "TextLayout.getOutline" };
 118 
 119     public final int RANGE_TEXT = 0;
 120     public final int ALL_GLYPHS = 1;
 121     public final int USER_TEXT = 2;
 122     public final int FILE_TEXT = 3;
 123     private final String[] MS_OPENING =
 124       { " Unicode ", " Glyph Code ", " lines ", " lines " };
 125     private final String[] MS_CLOSING =
 126       { "", "", " of User Text ", " of LineBreakMeasurer-reformatted Text " };
 127 
 128     /// General Graphics Variable
 129     private final JScrollBar verticalBar;
 130     private final FontCanvas fc;
 131     private boolean updateFontMetrics = true;
 132     private boolean updateFont = true;
 133     private boolean force16Cols = false;
 134     public boolean showingError = false;
 135     private int g2Transform = NONE; /// ABP
 136 
 137     /// Printing constants and variables
 138     public final int ONE_PAGE = 0;
 139     public final int CUR_RANGE = 1;
 140     public final int ALL_TEXT = 2;
 141     private int printMode = ONE_PAGE;
 142     private PageFormat page = null;
 143     private PrinterJob printer = null;
 144 
 145     /// Text drawing variables
 146     private String fontName = "Dialog";
 147     private float fontSize = 12;
 148     private int fontStyle = Font.PLAIN;
 149     private int fontTransform = NONE;
 150     private Font testFont = null;
 151     private Object antiAliasType = VALUE_TEXT_ANTIALIAS_DEFAULT;
 152     private Object fractionalMetricsType = VALUE_FRACTIONALMETRICS_DEFAULT;
 153     private Object lcdContrast = getDefaultLCDContrast();
 154     private int drawMethod = DRAW_STRING;
 155     private int textToUse = RANGE_TEXT;
 156     private String[] userText = null;
 157     private String[] fileText = null;
 158     private int[] drawRange = { 0x0000, 0x007f };
 159     private String[] fontInfos = new String[2];
 160     private boolean showGrid = true;
 161 
 162     /// Parent Font2DTest panel
 163     private final Font2DTest f2dt;
 164     private final JFrame parent;
 165 
 166     public FontPanel( Font2DTest demo, JFrame f ) {
 167         f2dt = demo;
 168         parent = f;
 169 
 170         verticalBar = new JScrollBar ( JScrollBar.VERTICAL );
 171         fc = new FontCanvas();
 172 
 173         this.setLayout( new BorderLayout() );
 174         this.add( "Center", fc );
 175         this.add( "East", verticalBar );
 176 
 177         verticalBar.addAdjustmentListener( this );
 178         this.addComponentListener( new ComponentAdapter() {
 179             public void componentResized( ComponentEvent e ) {
 180                 updateFontMetrics = true;
 181             }
 182         });
 183 
 184         /// Initialize font and its infos
 185         testFont = new Font(fontName, fontStyle, (int)fontSize);
 186         if ((float)((int)fontSize) != fontSize) {
 187             testFont = testFont.deriveFont(fontSize);
 188         }
 189         updateFontInfo();
 190     }
 191 
 192     public Dimension getPreferredSize() {
 193         return new Dimension(600, 200);
 194     }
 195 
 196     /// Functions called by the main programs to set the various parameters
 197 
 198     public void setTransformG2( int transform ) {
 199         g2Transform = transform;
 200         updateFontMetrics = true;
 201         fc.repaint();
 202     }
 203 
 204     /// convenience fcn to create AffineTransform of appropriate type
 205     private AffineTransform getAffineTransform( int transform ) {
 206             /// ABP
 207             AffineTransform at = new AffineTransform();
 208             switch ( transform )
 209             {
 210             case SCALE:
 211               at.setToScale( 1.5f, 1.5f ); break;
 212             case ROTATE:
 213               at.setToRotation( Math.PI / 6 ); break;
 214             case SHEAR:
 215               at.setToShear( 0.4f, 0 ); break;
 216             case NONE:
 217               break;
 218             default:
 219               //System.err.println( "Illegal G2 Transform Arg: " + transform);
 220               break;
 221             }
 222 
 223             return at;
 224     }
 225 
 226     public void setFontParams(Object obj, float size,
 227                               int style, int transform) {
 228         setFontParams( (String)obj, size, style, transform );
 229     }
 230 
 231     public void setFontParams(String name, float size,
 232                               int style, int transform) {
 233         boolean fontModified = false;
 234         if ( !name.equals( fontName ) || style != fontStyle )
 235           fontModified = true;
 236 
 237         fontName = name;
 238         fontSize = size;
 239         fontStyle = style;
 240         fontTransform = transform;
 241 
 242         /// Recreate the font as specified
 243         testFont = new Font(fontName, fontStyle, (int)fontSize);
 244         if ((float)((int)fontSize) != fontSize) {
 245             testFont = testFont.deriveFont(fontSize);
 246         }
 247 
 248         if ( fontTransform != NONE ) {
 249             AffineTransform at = getAffineTransform( fontTransform );
 250             testFont = testFont.deriveFont( at );
 251         }
 252         updateFontMetrics = true;
 253         fc.repaint();
 254         if ( fontModified ) {
 255             /// Tell main panel to update the font info
 256             updateFontInfo();
 257             f2dt.fireUpdateFontInfo();
 258         }
 259     }
 260 
 261     public void setRenderingHints( Object aa, Object fm, Object contrast) {
 262         antiAliasType = ((AAValues)aa).getHint();
 263         fractionalMetricsType = ((FMValues)fm).getHint();
 264         lcdContrast = contrast;
 265         updateFontMetrics = true;
 266         fc.repaint();
 267     }
 268 
 269     public void setDrawMethod( int i ) {
 270         drawMethod = i;
 271         fc.repaint();
 272     }
 273 
 274     public void setTextToDraw( int i, int[] range,
 275                                String[] textSet, String[] fileData ) {
 276         textToUse = i;
 277 
 278         if ( textToUse == RANGE_TEXT )
 279           drawRange = range;
 280         else if ( textToUse == ALL_GLYPHS )
 281           drawMethod = DRAW_GLYPHV;
 282         else if ( textToUse == USER_TEXT )
 283           userText = textSet;
 284         else if ( textToUse == FILE_TEXT ) {
 285             fileText = fileData;
 286             drawMethod = TL_DRAW;
 287         }
 288 
 289         updateFontMetrics = true;
 290         fc.repaint();
 291         updateFontInfo();
 292     }
 293 
 294     public void setGridDisplay( boolean b ) {
 295         showGrid = b;
 296         fc.repaint();
 297     }
 298 
 299     public void setForce16Columns( boolean b ) {
 300         force16Cols = b;
 301         updateFontMetrics = true;
 302         fc.repaint();
 303     }
 304 
 305     /// Prints out the text display area
 306     public void doPrint( int i ) {
 307         if ( printer == null ) {
 308             printer = PrinterJob.getPrinterJob();
 309             page = printer.defaultPage();
 310         }
 311         printMode = i;
 312         printer.setPrintable( fc, page );
 313 
 314         if ( printer.printDialog() ) {
 315             try {
 316                 printer.print();
 317             }
 318             catch ( Exception e ) {
 319                 f2dt.fireChangeStatus( "ERROR: Printing Failed; See Stack Trace", true );
 320             }
 321         }
 322     }
 323 
 324     /// Displays the page setup dialog and updates PageFormat info
 325     public void doPageSetup() {
 326         if ( printer == null ) {
 327             printer = PrinterJob.getPrinterJob();
 328             page = printer.defaultPage();
 329         }
 330         page = printer.pageDialog( page );
 331     }
 332 
 333     /// Obtains the information about selected font
 334     private void updateFontInfo() {
 335         int numGlyphs = 0, numCharsInRange = drawRange[1] - drawRange[0] + 1;
 336         fontInfos[0] = "Font Face Name: " + testFont.getFontName();
 337         fontInfos[1] = "Glyphs in This Range: ";
 338 
 339         if ( textToUse == RANGE_TEXT ) {
 340             for ( int i = drawRange[0]; i < drawRange[1]; i++ )
 341               if ( testFont.canDisplay( i ))
 342                 numGlyphs++;
 343             fontInfos[1] = fontInfos[1] + numGlyphs + " / " + numCharsInRange;
 344         }
 345         else
 346           fontInfos[1] = null;
 347     }
 348 
 349     /// Accessor for the font information
 350     public String[] getFontInfo() {
 351         return fontInfos;
 352     }
 353 
 354     /// Collects the currectly set options and returns them as string
 355     public String getCurrentOptions() {
 356         /// Create a new String to store the options
 357         /// The array will contain all 8 setting (font name, size...) and
 358         /// character range or user text data used (no file text data)
 359         int userTextSize = 0;
 360         String options;
 361 
 362         options = ( fontName + "\n" + fontSize  + "\n" + fontStyle + "\n" +
 363                     fontTransform + "\n"  + g2Transform + "\n"+
 364                     textToUse + "\n" + drawMethod + "\n" +
 365                     AAValues.getHintVal(antiAliasType) + "\n" +
 366                     FMValues.getHintVal(fractionalMetricsType) + "\n" +
 367                     lcdContrast + "\n");
 368         if ( textToUse == USER_TEXT )
 369           for ( int i = 0; i < userText.length; i++ )
 370             options += ( userText[i] + "\n" );
 371 
 372         return options;
 373     }
 374 
 375     /// Reload all options and refreshes the canvas
 376     public void loadOptions( boolean grid, boolean force16, int start, int end,
 377                              String name, float size, int style,
 378                              int transform, int g2transform,
 379                              int text, int method, int aa, int fm,
 380                              int contrast, String[] user ) {
 381         int[] range = { start, end };
 382 
 383         /// Since repaint call has a low priority, these functions will finish
 384         /// before the actual repainting is done
 385         setGridDisplay( grid );
 386         setForce16Columns( force16 );
 387         // previous call to readTextFile has already set the text to draw
 388         if (textToUse != FILE_TEXT) {
 389           setTextToDraw( text, range, user, null );
 390         }
 391         setFontParams( name, size, style, transform );
 392         setTransformG2( g2transform ); // ABP
 393         setDrawMethod( method );
 394         setRenderingHints(AAValues.getValue(aa), FMValues.getValue(fm),
 395                           new Integer(contrast));
 396     }
 397 
 398     /// Writes the current screen to PNG file
 399     public void doSavePNG( String fileName ) {
 400         fc.writePNG( fileName );
 401     }
 402 
 403     /// When scrolled using the scroll bar, update the backbuffer
 404     public void adjustmentValueChanged( AdjustmentEvent e ) {
 405         fc.repaint();
 406     }
 407 
 408     public void paintComponent( Graphics g ) {
 409         // Windows does not repaint correctly, after
 410         // a zoom. Thus, we need to force the canvas
 411         // to repaint, but only once. After the first repaint,
 412         // everything stabilizes. [ABP]
 413         fc.repaint();
 414     }
 415 
 416     /// Inner class definition...
 417 
 418     /// Inner panel that holds the actual drawing area and its routines
 419     private class FontCanvas extends JPanel implements MouseListener, MouseMotionListener, Printable {
 420 
 421         /// Number of characters that will fit across and down this canvas
 422         private int numCharAcross, numCharDown;
 423 
 424         /// First and last character/line that will be drawn
 425         /// Limit is the end of range/text where no more draw will be done
 426         private int drawStart, drawEnd, drawLimit;
 427 
 428         /// FontMetrics variables
 429         /// Here, gridWidth is equivalent to maxAdvance (slightly bigger though)
 430         /// and gridHeight is equivalent to lineHeight
 431         private int maxAscent, maxDescent, gridWidth = 0, gridHeight = 0;
 432 
 433         /// Offset from the top left edge of the canvas where the draw will start
 434         private int canvasInset_X = 5, canvasInset_Y = 5;
 435 
 436         /// LineBreak'ed TextLayout vector
 437         private Vector lineBreakTLs = null;
 438 
 439         /// Whether the current draw command requested is for printing
 440         private boolean isPrinting = false;
 441 
 442         /// Other printing infos
 443         private int lastPage, printPageNumber, currentlyShownChar = 0;
 444         private final int PR_OFFSET = 10;
 445         private final int PR_TITLE_LINEHEIGHT = 30;
 446 
 447         /// Information about zooming (used with range text draw)
 448         private final JWindow zoomWindow;
 449         private BufferedImage zoomImage = null;
 450         private int mouseOverCharX = -1, mouseOverCharY = -1;
 451         private int currMouseOverChar = -1, prevZoomChar = -1;
 452         private float ZOOM = 2.0f;
 453         private boolean nowZooming = false;
 454         private boolean firstTime = true;
 455 // ABP
 456 
 457         /// Status bar message backup
 458         private String backupStatusString = null;
 459 
 460         /// Error constants
 461         private final String[] ERRORS = {
 462             "ERROR: drawBytes cannot handle characters beyond 0x00FF. Select different range or draw methods.",
 463             "ERROR: Cannot fit text with the current font size. Resize the window or use smaller font size.",
 464             "ERROR: Cannot print with the current font size. Use smaller font size.",
 465         };
 466 
 467         private final int DRAW_BYTES_ERROR = 0;
 468         private final int CANT_FIT_DRAW = 1;
 469         private final int CANT_FIT_PRINT = 2;
 470 
 471         /// Other variables
 472         private final Cursor blankCursor;
 473 
 474         public FontCanvas() {
 475             this.addMouseListener( this );
 476             this.addMouseMotionListener( this );
 477             this.setForeground( Color.black );
 478             this.setBackground( Color.white );
 479 
 480             /// Creates an invisble pointer by giving it bogus image
 481             /// Possibly find a workaround for this...
 482             Toolkit tk = Toolkit.getDefaultToolkit();
 483             byte[] bogus = { (byte) 0 };
 484             blankCursor =
 485               tk.createCustomCursor( tk.createImage( bogus ), new Point(0, 0), "" );
 486 
 487             zoomWindow = new JWindow( parent ) {
 488                 public void paint( Graphics g ) {
 489                     g.drawImage( zoomImage, 0, 0, zoomWindow );
 490                 }
 491             };
 492             zoomWindow.setCursor( blankCursor );
 493             zoomWindow.pack();
 494         }
 495 
 496         public boolean firstTime() { return firstTime; }
 497         public void refresh() {
 498             firstTime = false;
 499             repaint();
 500         }
 501 
 502         /// Sets the font, hints, according to the set parameters
 503         private void setParams( Graphics2D g2 ) {
 504             g2.setFont( testFont );
 505             g2.setRenderingHint(KEY_TEXT_ANTIALIASING, antiAliasType);
 506             g2.setRenderingHint(KEY_FRACTIONALMETRICS, fractionalMetricsType);
 507             g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, lcdContrast);
 508             /* I am preserving a somewhat dubious behaviour of this program.
 509              * Outline text would be drawn anti-aliased by setting the
 510              * graphics anti-aliasing hint if the text anti-aliasing hint
 511              * was set. The dubious element here is that people simply
 512              * using this program may think this is built-in behaviour
 513              * but its not - at least not when the app explicitly draws
 514              * outline text.
 515              * This becomes more dubious in cases such as "GASP" where the
 516              * size at which text is AA'ed is not something you can easily
 517              * calculate, so mimicing that behaviour isn't going to be easy.
 518              * So I precisely preserve the behaviour : this is done only
 519              * if the AA value is "ON". Its not applied in the other cases.
 520              */
 521             if (antiAliasType == VALUE_TEXT_ANTIALIAS_ON &&
 522                 (drawMethod == TL_OUTLINE || drawMethod == GV_OUTLINE)) {
 523                 g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
 524             } else {
 525                 g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
 526             }
 527         }
 528 
 529         /// Draws the grid (Used for unicode/glyph range drawing)
 530         private void drawGrid( Graphics2D g2 ) {
 531             int totalGridWidth = numCharAcross * gridWidth;
 532             int totalGridHeight = numCharDown * gridHeight;
 533 
 534             g2.setColor( Color.black );
 535             for ( int i = 0; i < numCharDown + 1; i++ )
 536               g2.drawLine( canvasInset_X, i * gridHeight + canvasInset_Y,
 537                            canvasInset_X + totalGridWidth, i * gridHeight + canvasInset_Y );
 538             for ( int i = 0; i < numCharAcross + 1; i++ )
 539               g2.drawLine( i * gridWidth + canvasInset_X, canvasInset_Y,
 540                            i * gridWidth + canvasInset_X, canvasInset_Y + totalGridHeight );
 541         }
 542 
 543         /// Draws one character at time onto the canvas according to
 544         /// the method requested (Used for RANGE_TEXT and ALL_GLYPHS)
 545         public void modeSpecificDrawChar( Graphics2D g2, int charCode,
 546                                           int baseX, int baseY ) {
 547             GlyphVector gv;
 548             int[] oneGlyph = { charCode };
 549             char[] charArray = Character.toChars( charCode );
 550 
 551             FontRenderContext frc = g2.getFontRenderContext();
 552             AffineTransform oldTX = g2.getTransform();
 553 
 554             /// Create GlyphVector to measure the exact visual advance
 555             /// Using that number, adjust the position of the character drawn
 556             if ( textToUse == ALL_GLYPHS )
 557               gv = testFont.createGlyphVector( frc, oneGlyph );
 558             else
 559               gv = testFont.createGlyphVector( frc, charArray );
 560             Rectangle2D r2d2 = gv.getPixelBounds(frc, 0, 0);
 561             int shiftedX = baseX;
 562             // getPixelBounds returns a result in device space.
 563             // we need to convert back to user space to be able to
 564             // calculate the shift as baseX is in user space.
 565             try {
 566                  double[] pt = new double[4];
 567                  pt[0] = r2d2.getX();
 568                  pt[1] = r2d2.getY();
 569                  pt[2] = r2d2.getX()+r2d2.getWidth();
 570                  pt[3] = r2d2.getY()+r2d2.getHeight();
 571                  oldTX.inverseTransform(pt,0,pt,0,2);
 572                  shiftedX = baseX - (int) ( pt[2] / 2 + pt[0] );
 573             } catch (NoninvertibleTransformException e) {
 574             }
 575 
 576             /// ABP - keep track of old tform, restore it later
 577 
 578             g2.translate( shiftedX, baseY );
 579             g2.transform( getAffineTransform( g2Transform ) );
 580 
 581             if ( textToUse == ALL_GLYPHS )
 582               g2.drawGlyphVector( gv, 0f, 0f );
 583             else {
 584                 if ( testFont.canDisplay( charCode ))
 585                   g2.setColor( Color.black );
 586                 else {
 587                   g2.setColor( Color.lightGray );
 588                 }
 589 
 590                 switch ( drawMethod ) {
 591                   case DRAW_STRING:
 592                     g2.drawString( new String( charArray ), 0, 0 );
 593                     break;
 594                   case DRAW_CHARS:
 595                     g2.drawChars( charArray, 0, 1, 0, 0 );
 596                     break;
 597                   case DRAW_BYTES:
 598                     if ( charCode > 0xff )
 599                       throw new CannotDrawException( DRAW_BYTES_ERROR );
 600                     byte[] oneByte = { (byte) charCode };
 601                     g2.drawBytes( oneByte, 0, 1, 0, 0 );
 602                     break;
 603                   case DRAW_GLYPHV:
 604                     g2.drawGlyphVector( gv, 0f, 0f );
 605                     break;
 606                   case TL_DRAW:
 607                     TextLayout tl = new TextLayout( new String( charArray ), testFont, frc );
 608                     tl.draw( g2, 0f, 0f );
 609                     break;
 610                   case GV_OUTLINE:
 611                     r2d2 = gv.getVisualBounds();
 612                     shiftedX = baseX - (int) ( r2d2.getWidth() / 2 + r2d2.getX() );
 613                     g2.draw( gv.getOutline( 0f, 0f ));
 614                     break;
 615                   case TL_OUTLINE:
 616                     r2d2 = gv.getVisualBounds();
 617                     shiftedX = baseX - (int) ( r2d2.getWidth() / 2 + r2d2.getX() );
 618                     TextLayout tlo =
 619                       new TextLayout( new String( charArray ), testFont,
 620                                       g2.getFontRenderContext() );
 621                     g2.draw( tlo.getOutline( null ));
 622                 }
 623             }
 624 
 625             /// ABP - restore old tform
 626             g2.setTransform ( oldTX );
 627         }
 628 
 629         /// Draws one line of text at given position
 630         private void modeSpecificDrawLine( Graphics2D g2, String line,
 631                                            int baseX, int baseY ) {
 632             /// ABP - keep track of old tform, restore it later
 633             AffineTransform oldTx = null;
 634             oldTx = g2.getTransform();
 635             g2.translate( baseX, baseY );
 636             g2.transform( getAffineTransform( g2Transform ) );
 637 
 638             switch ( drawMethod ) {
 639               case DRAW_STRING:
 640                 g2.drawString( line, 0, 0 );
 641                 break;
 642               case DRAW_CHARS:
 643                 g2.drawChars( line.toCharArray(), 0, line.length(), 0, 0 );
 644                 break;
 645               case DRAW_BYTES:
 646                 try {
 647                     byte[] lineBytes = line.getBytes( "ISO-8859-1" );
 648                     g2.drawBytes( lineBytes, 0, lineBytes.length, 0, 0 );
 649                 }
 650                 catch ( Exception e ) {
 651                     e.printStackTrace();
 652                 }
 653                 break;
 654               case DRAW_GLYPHV:
 655                 GlyphVector gv =
 656                   testFont.createGlyphVector( g2.getFontRenderContext(), line );
 657                 g2.drawGlyphVector( gv, (float) 0, (float) 0 );
 658                 break;
 659               case TL_DRAW:
 660                 TextLayout tl = new TextLayout( line, testFont,
 661                                                 g2.getFontRenderContext() );
 662                 tl.draw( g2, (float) 0, (float) 0 );
 663                 break;
 664               case GV_OUTLINE:
 665                 GlyphVector gvo =
 666                   testFont.createGlyphVector( g2.getFontRenderContext(), line );
 667                 g2.draw( gvo.getOutline( (float) 0, (float) 0 ));
 668                 break;
 669               case TL_OUTLINE:
 670                 TextLayout tlo =
 671                   new TextLayout( line, testFont,
 672                                   g2.getFontRenderContext() );
 673                 AffineTransform at = new AffineTransform();
 674                 g2.draw( tlo.getOutline( at ));
 675             }
 676 
 677             /// ABP - restore old tform
 678             g2.setTransform ( oldTx );
 679 
 680         }
 681 
 682         /// Draws one line of text at given position
 683         private void tlDrawLine( Graphics2D g2, TextLayout tl,
 684                                            float baseX, float baseY ) {
 685             /// ABP - keep track of old tform, restore it later
 686             AffineTransform oldTx = null;
 687             oldTx = g2.getTransform();
 688             g2.translate( baseX, baseY );
 689             g2.transform( getAffineTransform( g2Transform ) );
 690 
 691             tl.draw( g2, (float) 0, (float) 0 );
 692 
 693             /// ABP - restore old tform
 694             g2.setTransform ( oldTx );
 695 
 696         }
 697 
 698 
 699         /// If textToUse is set to range drawing, then convert
 700         /// int to hex string and prepends 0s to make it length 4
 701         /// Otherwise line number was fed; simply return number + 1 converted to String
 702         /// (This is because first line is 1, not 0)
 703         private String modeSpecificNumStr( int i ) {
 704             if ( textToUse == USER_TEXT || textToUse == FILE_TEXT )
 705               return String.valueOf( i + 1 );
 706 
 707             StringBuffer s = new StringBuffer( Integer.toHexString( i ));
 708             while ( s.length() < 4 )
 709               s.insert( 0, "0" );
 710             return s.toString().toUpperCase();
 711         }
 712 
 713         /// Resets the scrollbar to display correct range of text currently on screen
 714         /// (This scrollbar is not part of a "ScrollPane". It merely simulates its effect by
 715         ///  indicating the necessary area to be drawn within the panel.
 716         ///  By doing this, it prevents creating gigantic panel when large text range,
 717         ///  i.e. CJK Ideographs, is requested)
 718         private void resetScrollbar( int oldValue ) {
 719             int totalNumRows = 1, numCharToDisplay;
 720             if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
 721                 if ( textToUse == RANGE_TEXT )
 722                   numCharToDisplay = drawRange[1] - drawRange[0];
 723                 else /// textToUse == ALL_GLYPHS
 724                   numCharToDisplay = testFont.getNumGlyphs();
 725 
 726                 totalNumRows = numCharToDisplay / numCharAcross;
 727                 if ( numCharToDisplay % numCharAcross != 0 )
 728                   totalNumRows++;
 729                 if ( oldValue / numCharAcross > totalNumRows )
 730                   oldValue = 0;
 731 
 732                 verticalBar.setValues( oldValue / numCharAcross,
 733                                        numCharDown, 0, totalNumRows );
 734             }
 735             else {
 736                 if ( textToUse == USER_TEXT )
 737                   totalNumRows = userText.length;
 738                 else /// textToUse == FILE_TEXT;
 739                   totalNumRows = lineBreakTLs.size();
 740                 verticalBar.setValues( oldValue, numCharDown, 0, totalNumRows );
 741             }
 742             if ( totalNumRows <= numCharDown && drawStart == 0) {
 743               verticalBar.setEnabled( false );
 744             }
 745             else {
 746               verticalBar.setEnabled( true );
 747             }
 748         }
 749 
 750         /// Calculates the font's metrics that will be used for draw
 751         private void calcFontMetrics( Graphics2D g2d, int w, int h ) {
 752             FontMetrics fm;
 753             Graphics2D g2 = (Graphics2D)g2d.create();
 754 
 755             /// ABP
 756             if ( g2Transform != NONE && textToUse != FILE_TEXT ) {
 757                 g2.setFont( g2.getFont().deriveFont( getAffineTransform( g2Transform )) );
 758                 fm = g2.getFontMetrics();
 759             }
 760             else {
 761                 fm = g2.getFontMetrics();
 762             }
 763 
 764             maxAscent = fm.getMaxAscent();
 765             maxDescent = fm.getMaxDescent();
 766             if (maxAscent == 0) maxAscent = 10;
 767             if (maxDescent == 0) maxDescent = 5;
 768             if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
 769                 /// Give slight extra room for each character
 770                 maxAscent += 3;
 771                 maxDescent += 3;
 772                 gridWidth = fm.getMaxAdvance() + 6;
 773                 gridHeight = maxAscent + maxDescent;
 774                 if ( force16Cols )
 775                   numCharAcross = 16;
 776                 else
 777                   numCharAcross = ( w - 10 ) / gridWidth;
 778                 numCharDown = ( h - 10 ) / gridHeight;
 779 
 780                 canvasInset_X = ( w - numCharAcross * gridWidth ) / 2;
 781                 canvasInset_Y = ( h - numCharDown * gridHeight ) / 2;
 782                 if ( numCharDown == 0 || numCharAcross == 0 )
 783                   throw new CannotDrawException( isPrinting ? CANT_FIT_PRINT : CANT_FIT_DRAW );
 784 
 785                 if ( !isPrinting )
 786                   resetScrollbar( verticalBar.getValue() * numCharAcross );
 787             }
 788             else {
 789                 maxDescent += fm.getLeading();
 790                 canvasInset_X = 5;
 791                 canvasInset_Y = 5;
 792                 /// gridWidth and numCharAcross will not be used in this mode...
 793                 gridHeight = maxAscent + maxDescent;
 794                 numCharDown = ( h - canvasInset_Y * 2 ) / gridHeight;
 795 
 796                 if ( numCharDown == 0 )
 797                   throw new CannotDrawException( isPrinting ? CANT_FIT_PRINT : CANT_FIT_DRAW );
 798                 /// If this is text loaded from file, prepares the LineBreak'ed
 799                 /// text layout at this point
 800                 if ( textToUse == FILE_TEXT ) {
 801                     if ( !isPrinting )
 802                       f2dt.fireChangeStatus( "LineBreaking Text... Please Wait", false );
 803                     lineBreakTLs = new Vector();
 804                     for ( int i = 0; i < fileText.length; i++ ) {
 805                         AttributedString as =
 806                           new AttributedString( fileText[i], g2.getFont().getAttributes() );
 807 
 808                         LineBreakMeasurer lbm =
 809                           new LineBreakMeasurer( as.getIterator(), g2.getFontRenderContext() );
 810 
 811                         while ( lbm.getPosition() < fileText[i].length() )
 812                           lineBreakTLs.add( lbm.nextLayout( (float) w ));
 813 
 814                     }
 815                 }
 816                 if ( !isPrinting )
 817                   resetScrollbar( verticalBar.getValue() );
 818             }
 819         }
 820 
 821         /// Calculates the amount of text that will be displayed on screen
 822         private void calcTextRange() {
 823             String displaying = null;
 824 
 825             if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
 826                 if ( isPrinting )
 827                   if ( printMode == ONE_PAGE )
 828                     drawStart = currentlyShownChar;
 829                   else /// printMode == CUR_RANGE
 830                     drawStart = numCharAcross * numCharDown * printPageNumber;
 831                 else
 832                   drawStart = verticalBar.getValue() * numCharAcross;
 833                 if ( textToUse == RANGE_TEXT ) {
 834                     drawStart += drawRange[0];
 835                     drawLimit = drawRange[1];
 836                 }
 837                 else
 838                   drawLimit = testFont.getNumGlyphs();
 839                 drawEnd = drawStart + numCharAcross * numCharDown - 1;
 840 
 841                 if ( drawEnd >= drawLimit )
 842                   drawEnd = drawLimit;
 843             }
 844             else {
 845                 if ( isPrinting )
 846                   if ( printMode == ONE_PAGE )
 847                     drawStart = currentlyShownChar;
 848                   else /// printMode == ALL_TEXT
 849                     drawStart = numCharDown * printPageNumber;
 850                 else {
 851                     drawStart = verticalBar.getValue();
 852                 }
 853 
 854                 drawEnd = drawStart + numCharDown - 1;
 855 
 856                 if ( textToUse == USER_TEXT )
 857                   drawLimit = userText.length - 1;
 858                 else
 859                   drawLimit = lineBreakTLs.size() - 1;
 860 
 861                 if ( drawEnd >= drawLimit )
 862                   drawEnd = drawLimit;
 863             }
 864 
 865             // ABP
 866             if ( drawStart > drawEnd ) {
 867               drawStart = 0;
 868               verticalBar.setValue(drawStart);
 869             }
 870 
 871 
 872             /// Change the status bar if not printing...
 873             if ( !isPrinting ) {
 874                 backupStatusString = ( "Displaying" + MS_OPENING[textToUse] +
 875                                        modeSpecificNumStr( drawStart ) + " to " +
 876                                        modeSpecificNumStr( drawEnd ) +
 877                                        MS_CLOSING[textToUse] );
 878                 f2dt.fireChangeStatus( backupStatusString, false );
 879             }
 880         }
 881 
 882         /// Draws text according to the parameters set by Font2DTest GUI
 883         private void drawText( Graphics g, int w, int h ) {
 884             Graphics2D g2 = (Graphics2D) g;
 885             g2.setColor(Color.white);
 886             g2.fillRect(0, 0, w, h);
 887             g2.setColor(Color.black);
 888 
 889             /// sets font, RenderingHints.
 890             setParams( g2 );
 891 
 892             /// If flag is set, recalculate fontMetrics and reset the scrollbar
 893             if ( updateFontMetrics || isPrinting ) {
 894                 /// NOTE: re-calculates in case G2 transform
 895                 /// is something other than NONE
 896                 calcFontMetrics( g2, w, h );
 897                 updateFontMetrics = false;
 898             }
 899             /// Calculate the amount of text that can be drawn...
 900             calcTextRange();
 901 
 902             /// Draw according to the set "Text to Use" mode
 903             if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
 904                 int charToDraw = drawStart;
 905                 if ( showGrid )
 906                   drawGrid( g2 );
 907 
 908                 for ( int i = 0; i < numCharDown && charToDraw <= drawEnd; i++ ) {
 909                   for ( int j = 0; j < numCharAcross && charToDraw <= drawEnd; j++, charToDraw++ ) {
 910                       int gridLocX = j * gridWidth + canvasInset_X;
 911                       int gridLocY = i * gridHeight + canvasInset_Y;
 912 
 913                       modeSpecificDrawChar( g2, charToDraw,
 914                                             gridLocX + gridWidth / 2,
 915                                             gridLocY + maxAscent );
 916 
 917                   }
 918                 }
 919             }
 920             else if ( textToUse == USER_TEXT ) {
 921                 g2.drawRect( 0, 0, w - 1, h - 1 );
 922                 for ( int i = drawStart; i <= drawEnd; i++ ) {
 923                     int lineStartX = canvasInset_Y;
 924                     int lineStartY = ( i - drawStart ) * gridHeight + maxAscent;
 925                     modeSpecificDrawLine( g2, userText[i], lineStartX, lineStartY );
 926                 }
 927             }
 928             else {
 929                 float xPos, yPos = (float) canvasInset_Y;
 930                 g2.drawRect( 0, 0, w - 1, h - 1 );
 931                 for ( int i = drawStart; i <= drawEnd; i++ ) {
 932                     TextLayout oneLine = (TextLayout) lineBreakTLs.elementAt( i );
 933                     xPos =
 934                       oneLine.isLeftToRight() ?
 935                       canvasInset_X : ( (float) w - oneLine.getAdvance() - canvasInset_X );
 936 
 937                     float[] fmData = {0, oneLine.getAscent(), 0, oneLine.getDescent(), 0, oneLine.getLeading()};
 938                     if (g2Transform != NONE) {
 939                         AffineTransform at = getAffineTransform(g2Transform);
 940                         at.transform( fmData, 0, fmData, 0, 3);
 941                     }
 942                     //yPos += oneLine.getAscent();
 943                     yPos += fmData[1]; // ascent
 944                     //oneLine.draw( g2, xPos, yPos );
 945                     tlDrawLine( g2, oneLine, xPos, yPos );
 946                     //yPos += oneLine.getDescent() + oneLine.getLeading();
 947                     yPos += fmData[3] + fmData[5]; // descent + leading
 948                 }
 949             }
 950             g2.dispose();
 951         }
 952 
 953         /// Component paintComponent function...
 954         /// Draws/Refreshes canvas according to flag(s) set by other functions
 955         public void paintComponent( Graphics g ) {
 956               super.paintComponent(g);
 957 
 958                 Dimension d = this.getSize();
 959                 isPrinting = false;
 960                 try {
 961                     drawText( g, d.width, d.height );
 962                 }
 963                 catch ( CannotDrawException e ) {
 964                     super.paintComponent(g);
 965                     f2dt.fireChangeStatus( ERRORS[ e.id ], true );
 966                     return;
 967                 }
 968 
 969             showingError = false;
 970         }
 971 
 972         /// Printable interface function
 973         /// Component print function...
 974         public int print( Graphics g, PageFormat pf, int pageIndex ) {
 975             if ( pageIndex == 0 ) {
 976                 /// Reset the last page index to max...
 977                 lastPage = Integer.MAX_VALUE;
 978                 currentlyShownChar = verticalBar.getValue() * numCharAcross;
 979             }
 980 
 981             if ( printMode == ONE_PAGE ) {
 982                 if ( pageIndex > 0 )
 983                   return NO_SUCH_PAGE;
 984             }
 985             else {
 986                 if ( pageIndex > lastPage )
 987                   return NO_SUCH_PAGE;
 988             }
 989 
 990             int pageWidth = (int) pf.getImageableWidth();
 991             int pageHeight = (int) pf.getImageableHeight();
 992             /// Back up metrics and other drawing info before printing modifies it
 993             int backupDrawStart = drawStart, backupDrawEnd = drawEnd;
 994             int backupNumCharAcross = numCharAcross, backupNumCharDown = numCharDown;
 995             Vector backupLineBreakTLs = null;
 996             if ( textToUse == FILE_TEXT )
 997               backupLineBreakTLs = (Vector) lineBreakTLs.clone();
 998 
 999             printPageNumber = pageIndex;
1000             isPrinting = true;
1001             /// Push the actual draw area 60 down to allow info to be printed
1002             g.translate( (int) pf.getImageableX(), (int) pf.getImageableY() + 60 );
1003             try {
1004                 drawText( g, pageWidth, pageHeight - 60 );
1005             }
1006             catch ( CannotDrawException e ) {
1007                 f2dt.fireChangeStatus( ERRORS[ e.id ], true );
1008                 return NO_SUCH_PAGE;
1009             }
1010 
1011             /// Draw information about what is being printed
1012             String hints = ( " with antialias " + antiAliasType + "and" +
1013                              " fractional metrics " + fractionalMetricsType +
1014                              " and lcd contrast = " + lcdContrast);
1015             String infoLine1 = ( "Printing" + MS_OPENING[textToUse] +
1016                                  modeSpecificNumStr( drawStart ) + " to " +
1017                                  modeSpecificNumStr( drawEnd ) + MS_CLOSING[textToUse] );
1018             String infoLine2 = ( "With " + fontName + " " + STYLES[fontStyle] + " at " +
1019                                  fontSize + " point size " + TRANSFORMS[fontTransform] );
1020             String infoLine3 = "Using " + METHODS[drawMethod] + hints;
1021             String infoLine4 = "Page: " + ( pageIndex + 1 );
1022             g.setFont( new Font( "dialog", Font.PLAIN, 12 ));
1023             g.setColor( Color.black );
1024             g.translate( 0, -60 );
1025             g.drawString( infoLine1, 15, 10 );
1026             g.drawString( infoLine2, 15, 22 );
1027             g.drawString( infoLine3, 15, 34 );
1028             g.drawString( infoLine4, 15, 46 );
1029 
1030             if ( drawEnd == drawLimit )
1031               /// This indicates that the draw will be completed with this page
1032               lastPage = pageIndex;
1033 
1034             /// Restore the changed values back...
1035             /// This is important for JScrollBar settings and LineBreak'ed TLs
1036             drawStart = backupDrawStart;
1037             drawEnd = backupDrawEnd;
1038             numCharAcross = backupNumCharAcross;
1039             numCharDown = backupNumCharDown;
1040             if ( textToUse == FILE_TEXT )
1041               lineBreakTLs = backupLineBreakTLs;
1042             return PAGE_EXISTS;
1043         }
1044 
1045         /// Ouputs the current canvas into a given PNG file
1046         public void writePNG( String fileName ) {
1047             try {
1048                 int w = this.getSize().width;
1049                 int h = this.getSize().height;
1050                 BufferedImage buffer = (BufferedImage) this.createImage( w, h );
1051                 Graphics2D g2 = buffer.createGraphics();
1052                 g2.setColor(Color.white);
1053                 g2.fillRect(0, 0, w, h);
1054                 g2.setColor(Color.black);
1055                 updateFontMetrics = true;
1056                 drawText(g2, w, h);
1057                 updateFontMetrics = true;
1058                 ImageIO.write(buffer, "png", new java.io.File(fileName));
1059             }
1060             catch ( Exception e ) {
1061                 f2dt.fireChangeStatus( "ERROR: Failed to Save PNG image; See stack trace", true );
1062                 e.printStackTrace();
1063             }
1064         }
1065 
1066         /// Figures out whether a character at the pointer location is valid
1067         /// And if so, updates mouse location informations, as well as
1068         /// the information on the status bar
1069         private boolean checkMouseLoc( MouseEvent e ) {
1070             if ( gridWidth != 0 && gridHeight != 0 )
1071               if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
1072                   int charLocX = ( e.getX() - canvasInset_X ) / gridWidth;
1073                   int charLocY = ( e.getY() - canvasInset_Y ) / gridHeight;
1074 
1075                   /// Check to make sure the mouse click location is within drawn area
1076                   if ( charLocX >= 0 && charLocY >= 0 &&
1077                        charLocX < numCharAcross && charLocY < numCharDown ) {
1078                       int mouseOverChar =
1079                         charLocX + ( verticalBar.getValue() + charLocY ) * numCharAcross;
1080                       if ( textToUse == RANGE_TEXT )
1081                         mouseOverChar += drawRange[0];
1082                       if ( mouseOverChar > drawEnd )
1083                         return false;
1084 
1085                       mouseOverCharX = charLocX;
1086                       mouseOverCharY = charLocY;
1087                       currMouseOverChar = mouseOverChar;
1088                       /// Update status bar
1089                       f2dt.fireChangeStatus( "Pointing to" + MS_OPENING[textToUse] +
1090                                              modeSpecificNumStr( mouseOverChar ), false );
1091                       return true;
1092                   }
1093               }
1094             return false;
1095         }
1096 
1097         /// Shows (updates) the character zoom window
1098         public void showZoomed() {
1099             GlyphVector gv;
1100             Font backup = testFont;
1101             Point canvasLoc = this.getLocationOnScreen();
1102 
1103             /// Calculate the zoom area's location and size...
1104             int dialogOffsetX = (int) ( gridWidth * ( ZOOM - 1 ) / 2 );
1105             int dialogOffsetY = (int) ( gridHeight * ( ZOOM - 1 ) / 2 );
1106             int zoomAreaX =
1107               mouseOverCharX * gridWidth + canvasInset_X - dialogOffsetX;
1108             int zoomAreaY =
1109               mouseOverCharY * gridHeight + canvasInset_Y - dialogOffsetY;
1110             int zoomAreaWidth = (int) ( gridWidth * ZOOM );
1111             int zoomAreaHeight = (int) ( gridHeight * ZOOM );
1112 
1113             /// Position and set size of zoom window as needed
1114             zoomWindow.setLocation( canvasLoc.x + zoomAreaX, canvasLoc.y + zoomAreaY );
1115             if ( !nowZooming ) {
1116                 if ( zoomWindow.getWarningString() != null )
1117                   /// If this is not opened as a "secure" window,
1118                   /// it has a banner below the zoom dialog which makes it look really BAD
1119                   /// So enlarge it by a bit
1120                   zoomWindow.setSize( zoomAreaWidth + 1, zoomAreaHeight + 20 );
1121                 else
1122                   zoomWindow.setSize( zoomAreaWidth + 1, zoomAreaHeight + 1 );
1123             }
1124 
1125             /// Prepare zoomed image
1126             zoomImage =
1127               (BufferedImage) zoomWindow.createImage( zoomAreaWidth + 1,
1128                                                       zoomAreaHeight + 1 );
1129             Graphics2D g2 = (Graphics2D) zoomImage.getGraphics();
1130             testFont = testFont.deriveFont( fontSize * ZOOM );
1131             setParams( g2 );
1132             g2.setColor( Color.white );
1133             g2.fillRect( 0, 0, zoomAreaWidth, zoomAreaHeight );
1134             g2.setColor( Color.black );
1135             g2.drawRect( 0, 0, zoomAreaWidth, zoomAreaHeight );
1136             modeSpecificDrawChar( g2, currMouseOverChar,
1137                                   zoomAreaWidth / 2, (int) ( maxAscent * ZOOM ));
1138             g2.dispose();
1139             if ( !nowZooming )
1140               zoomWindow.show();
1141             /// This is sort of redundant... since there is a paint function
1142             /// inside zoomWindow definition that does the drawImage.
1143             /// (I should be able to call just repaint() here)
1144             /// However, for some reason, that paint function fails to respond
1145             /// from second time and on; So I have to force the paint here...
1146             zoomWindow.getGraphics().drawImage( zoomImage, 0, 0, this );
1147 
1148             nowZooming = true;
1149             prevZoomChar = currMouseOverChar;
1150             testFont = backup;
1151 
1152             // Windows does not repaint correctly, after
1153             // a zoom. Thus, we need to force the canvas
1154             // to repaint, but only once. After the first repaint,
1155             // everything stabilizes. [ABP]
1156             if ( firstTime() ) {
1157                 refresh();
1158             }
1159         }
1160 
1161         /// Listener Functions
1162 
1163         /// MouseListener interface function
1164         /// Zooms a character when mouse is pressed above it
1165         public void mousePressed( MouseEvent e ) {
1166             if ( !showingError) {
1167                 if ( checkMouseLoc( e )) {
1168                     showZoomed();
1169                     this.setCursor( blankCursor );
1170                 }
1171             }
1172         }
1173 
1174         /// MouseListener interface function
1175         /// Redraws the area that was drawn over by zoomed character
1176         public void mouseReleased( MouseEvent e ) {
1177             if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {
1178                 if ( nowZooming )
1179                   zoomWindow.hide();
1180                 nowZooming = false;
1181             }
1182             this.setCursor( Cursor.getDefaultCursor() );
1183         }
1184 
1185         /// MouseListener interface function
1186         /// Resets the status bar to display range instead of a specific character
1187         public void mouseExited( MouseEvent e ) {
1188             if ( !showingError && !nowZooming )
1189               f2dt.fireChangeStatus( backupStatusString, false );
1190         }
1191 
1192         /// MouseMotionListener interface function
1193         /// Adjusts the status bar message when mouse moves over a character
1194         public void mouseMoved( MouseEvent e ) {
1195             if ( !showingError ) {
1196                 if ( !checkMouseLoc( e ))
1197                   f2dt.fireChangeStatus( backupStatusString, false );
1198             }
1199         }
1200 
1201         /// MouseMotionListener interface function
1202         /// Scrolls the zoomed character when mouse is dragged
1203         public void mouseDragged( MouseEvent e ) {
1204             if ( !showingError )
1205               if ( nowZooming ) {
1206                   if ( checkMouseLoc( e ) && currMouseOverChar != prevZoomChar )
1207                     showZoomed();
1208               }
1209         }
1210 
1211         /// Empty function to comply with interface requirement
1212         public void mouseClicked( MouseEvent e ) {}
1213         public void mouseEntered( MouseEvent e ) {}
1214     }
1215 
1216     private final class CannotDrawException extends RuntimeException {
1217         /// Error ID
1218         public final int id;
1219 
1220         public CannotDrawException( int i ) {
1221             id = i;
1222         }
1223     }
1224 
1225     enum FMValues {
1226        FMDEFAULT ("DEFAULT",  VALUE_FRACTIONALMETRICS_DEFAULT),
1227        FMOFF     ("OFF",      VALUE_FRACTIONALMETRICS_OFF),
1228        FMON      ("ON",       VALUE_FRACTIONALMETRICS_ON);
1229 
1230         private String name;
1231         private Object hint;
1232 
1233         private static FMValues[] valArray;
1234 
1235         FMValues(String s, Object o) {
1236             name = s;
1237             hint = o;
1238         }
1239 
1240         public String toString() {
1241             return name;
1242         }
1243 
1244        public Object getHint() {
1245            return hint;
1246        }
1247        public static Object getValue(int ordinal) {
1248            if (valArray == null) {
1249                valArray = (FMValues[])EnumSet.allOf(FMValues.class).toArray(new FMValues[0]);
1250            }
1251            for (int i=0;i<valArray.length;i++) {
1252                if (valArray[i].ordinal() == ordinal) {
1253                    return valArray[i];
1254                }
1255            }
1256            return valArray[0];
1257        }
1258        private static FMValues[] getArray() {
1259            if (valArray == null) {
1260                valArray = (FMValues[])EnumSet.allOf(FMValues.class).toArray(new FMValues[0]);
1261            }
1262            return valArray;
1263        }
1264 
1265        public static int getHintVal(Object hint) {
1266            getArray();
1267            for (int i=0;i<valArray.length;i++) {
1268                if (valArray[i].getHint() == hint) {
1269                    return i;
1270                }
1271            }
1272            return 0;
1273        }
1274     }
1275 
1276    enum AAValues {
1277        AADEFAULT ("DEFAULT",  VALUE_TEXT_ANTIALIAS_DEFAULT),
1278        AAOFF     ("OFF",      VALUE_TEXT_ANTIALIAS_OFF),
1279        AAON      ("ON",       VALUE_TEXT_ANTIALIAS_ON),
1280        AAGASP    ("GASP",     VALUE_TEXT_ANTIALIAS_GASP),
1281        AALCDHRGB ("LCD_HRGB", VALUE_TEXT_ANTIALIAS_LCD_HRGB),
1282        AALCDHBGR ("LCD_HBGR", VALUE_TEXT_ANTIALIAS_LCD_HBGR),
1283        AALCDVRGB ("LCD_VRGB", VALUE_TEXT_ANTIALIAS_LCD_VRGB),
1284        AALCDVBGR ("LCD_VBGR", VALUE_TEXT_ANTIALIAS_LCD_VBGR);
1285 
1286         private String name;
1287         private Object hint;
1288 
1289         private static AAValues[] valArray;
1290 
1291         AAValues(String s, Object o) {
1292             name = s;
1293             hint = o;
1294         }
1295 
1296         public String toString() {
1297             return name;
1298         }
1299 
1300        public Object getHint() {
1301            return hint;
1302        }
1303 
1304        public static boolean isLCDMode(Object o) {
1305            return (o instanceof AAValues &&
1306                    ((AAValues)o).ordinal() >= AALCDHRGB.ordinal());
1307        }
1308 
1309        public static Object getValue(int ordinal) {
1310            if (valArray == null) {
1311                valArray = (AAValues[])EnumSet.allOf(AAValues.class).toArray(new AAValues[0]);
1312            }
1313            for (int i=0;i<valArray.length;i++) {
1314                if (valArray[i].ordinal() == ordinal) {
1315                    return valArray[i];
1316                }
1317            }
1318            return valArray[0];
1319        }
1320 
1321        private static AAValues[] getArray() {
1322            if (valArray == null) {
1323                Object [] oa = EnumSet.allOf(AAValues.class).toArray(new AAValues[0]);
1324                valArray = (AAValues[])(EnumSet.allOf(AAValues.class).toArray(new AAValues[0]));
1325            }
1326            return valArray;
1327        }
1328 
1329        public static int getHintVal(Object hint) {
1330            getArray();
1331            for (int i=0;i<valArray.length;i++) {
1332                if (valArray[i].getHint() == hint) {
1333                    return i;
1334                }
1335            }
1336            return 0;
1337        }
1338 
1339     }
1340 
1341     private static Integer defaultContrast;
1342     static Integer getDefaultLCDContrast() {
1343         if (defaultContrast == null) {
1344             GraphicsConfiguration gc =
1345             GraphicsEnvironment.getLocalGraphicsEnvironment().
1346                 getDefaultScreenDevice().getDefaultConfiguration();
1347         Graphics2D g2d =
1348             (Graphics2D)(gc.createCompatibleImage(1,1).getGraphics());
1349         defaultContrast = (Integer)
1350             g2d.getRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST);
1351         }
1352         return defaultContrast;
1353     }
1354 }