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