1/*
2 * Copyright (c) 1999, 2011, 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
42import java.awt.Color;
43import java.awt.Component;
44import java.awt.BorderLayout;
45import java.awt.CheckboxGroup;
46import java.awt.Container;
47import java.awt.Dimension;
48import java.awt.Font;
49import java.awt.Graphics;
50import java.awt.Graphics2D;
51import java.awt.GraphicsEnvironment;
52import java.awt.GridBagConstraints;
53import java.awt.GridBagLayout;
54import java.awt.GridLayout;
55import java.awt.Insets;
56import java.awt.RenderingHints;
57import java.awt.Toolkit;
58import java.awt.event.ActionEvent;
59import java.awt.event.ActionListener;
60import java.awt.event.ItemEvent;
61import java.awt.event.ItemListener;
62import java.awt.event.WindowAdapter;
63import java.awt.event.WindowEvent;
64import java.awt.image.BufferedImage;
65import java.io.BufferedInputStream;
66import java.io.BufferedOutputStream;
67import java.io.File;
68import java.io.FileInputStream;
69import java.io.FileOutputStream;
70import java.util.EnumSet;
71import java.util.StringTokenizer;
72import java.util.BitSet;
73import javax.swing.*;
74import javax.swing.event.*;
75
76/**
77 * Font2DTest.java
78 *
79 * @author Shinsuke Fukuda
80 * @author Ankit Patel [Conversion to Swing - 01/07/30]
81 */
82
83/// Main Font2DTest Class
84
85public final class Font2DTest extends JPanel
86    implements ActionListener, ItemListener, ChangeListener {
87
88    /// JFrame that will contain Font2DTest
89    private final JFrame parent;
90    /// FontPanel class that will contain all graphical output
91    private final FontPanel fp;
92    /// RangeMenu class that contains info about the unicode ranges
93    private final RangeMenu rm;
94
95    /// Other menus to set parameters for text drawing
96    private final ChoiceV2 fontMenu;
97    private final JTextField sizeField;
98    private final ChoiceV2 styleMenu;
99    private final ChoiceV2 textMenu;
100    private int currentTextChoice = 0;
101    private final ChoiceV2 transformMenu;
102    private final ChoiceV2 transformMenuG2;
103    private final ChoiceV2 methodsMenu;
104    private final JComboBox antiAliasMenu;
105    private final JComboBox fracMetricsMenu;
106
107    private final JSlider contrastSlider;
108
109    /// CheckboxMenuItems
110    private CheckboxMenuItemV2 displayGridCBMI;
111    private CheckboxMenuItemV2 force16ColsCBMI;
112    private CheckboxMenuItemV2 showFontInfoCBMI;
113
114    /// JDialog boxes
115    private JDialog userTextDialog;
116    private JTextArea userTextArea;
117    private JDialog printDialog;
118    private JDialog fontInfoDialog;
119    private LabelV2 fontInfos[] = new LabelV2[2];
120    private JFileChooser filePromptDialog = null;
121
122    private ButtonGroup printCBGroup;
123    private JRadioButton printModeCBs[] = new JRadioButton[3];
124
125    /// Status bar
126    private final LabelV2 statusBar;
127
128    private int fontStyles [] = {Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC};
129
130    /// Text filename
131    private String tFileName;
132
133    // Enabled or disabled status of canDisplay check
134    private static boolean canDisplayCheck = true;
135
136    /// Initialize GUI variables and its layouts
137    public Font2DTest( JFrame f, boolean isApplet ) {
138        parent = f;
139
140        rm = new RangeMenu( this, parent );
141        fp = new FontPanel( this, parent );
142        statusBar = new LabelV2("");
143
144        fontMenu = new ChoiceV2( this, canDisplayCheck );
145        sizeField = new JTextField( "12", 3 );
146        sizeField.addActionListener( this );
147        styleMenu = new ChoiceV2( this );
148        textMenu = new ChoiceV2( ); // listener added later
149        transformMenu = new ChoiceV2( this );
150        transformMenuG2 = new ChoiceV2( this );
151        methodsMenu = new ChoiceV2( this );
152
153        antiAliasMenu =
154            new JComboBox(EnumSet.allOf(FontPanel.AAValues.class).toArray());
155        antiAliasMenu.addActionListener(this);
156        fracMetricsMenu =
157            new JComboBox(EnumSet.allOf(FontPanel.FMValues.class).toArray());
158        fracMetricsMenu.addActionListener(this);
159
160        contrastSlider = new JSlider(JSlider.HORIZONTAL, 100, 250,
161                                 FontPanel.getDefaultLCDContrast().intValue());
162        contrastSlider.setEnabled(false);
163        contrastSlider.setMajorTickSpacing(20);
164        contrastSlider.setMinorTickSpacing(10);
165        contrastSlider.setPaintTicks(true);
166        contrastSlider.setPaintLabels(true);
167        contrastSlider.addChangeListener(this);
168        setupPanel();
169        setupMenu( isApplet );
170        setupDialog( isApplet );
171
172        if(canDisplayCheck) {
173            fireRangeChanged();
174        }
175    }
176
177    /// Set up the main interface panel
178    private void setupPanel() {
179        GridBagLayout gbl = new GridBagLayout();
180        GridBagConstraints gbc = new GridBagConstraints();
181        gbc.fill = GridBagConstraints.HORIZONTAL;
182        gbc.weightx = 1;
183        gbc.insets = new Insets( 2, 0, 2, 2 );
184        this.setLayout( gbl );
185
186        addLabeledComponentToGBL( "Font: ", fontMenu, gbl, gbc, this );
187        addLabeledComponentToGBL( "Size: ", sizeField, gbl, gbc, this );
188        gbc.gridwidth = GridBagConstraints.REMAINDER;
189        addLabeledComponentToGBL( "Font Transform:",
190                                  transformMenu, gbl, gbc, this );
191        gbc.gridwidth = 1;
192
193        addLabeledComponentToGBL( "Range: ", rm, gbl, gbc, this );
194        addLabeledComponentToGBL( "Style: ", styleMenu, gbl, gbc, this );
195        gbc.gridwidth = GridBagConstraints.REMAINDER;
196        addLabeledComponentToGBL( "Graphics Transform: ",
197                                  transformMenuG2, gbl, gbc, this );
198        gbc.gridwidth = 1;
199
200        gbc.anchor = GridBagConstraints.WEST;
201        addLabeledComponentToGBL( "Method: ", methodsMenu, gbl, gbc, this );
202        addLabeledComponentToGBL("", null, gbl, gbc, this);
203        gbc.anchor = GridBagConstraints.EAST;
204        gbc.gridwidth = GridBagConstraints.REMAINDER;
205        addLabeledComponentToGBL( "Text to use:", textMenu, gbl, gbc, this );
206
207        gbc.weightx=1;
208        gbc.gridwidth = 1;
209        gbc.fill = GridBagConstraints.HORIZONTAL;
210        gbc.anchor = GridBagConstraints.WEST;
211        addLabeledComponentToGBL("LCD contrast: ",
212                                  contrastSlider, gbl, gbc, this);
213
214        gbc.gridwidth = 1;
215        gbc.fill = GridBagConstraints.NONE;
216        addLabeledComponentToGBL("Antialiasing: ",
217                                  antiAliasMenu, gbl, gbc, this);
218
219        gbc.anchor = GridBagConstraints.EAST;
220        gbc.gridwidth = GridBagConstraints.REMAINDER;
221        addLabeledComponentToGBL("Fractional metrics: ",
222                                  fracMetricsMenu, gbl, gbc, this);
223
224        gbc.weightx = 1;
225        gbc.weighty = 1;
226        gbc.anchor = GridBagConstraints.WEST;
227        gbc.insets = new Insets( 2, 0, 0, 2 );
228        gbc.fill = GridBagConstraints.BOTH;
229        gbl.setConstraints( fp, gbc );
230        this.add( fp );
231
232        gbc.weighty = 0;
233        gbc.insets = new Insets( 0, 2, 0, 0 );
234        gbl.setConstraints( statusBar, gbc );
235        this.add( statusBar );
236    }
237
238    /// Adds a component to a container with a label to its left in GridBagLayout
239    private void addLabeledComponentToGBL( String name,
240                                           JComponent c,
241                                           GridBagLayout gbl,
242                                           GridBagConstraints gbc,
243                                           Container target ) {
244        LabelV2 l = new LabelV2( name );
245        GridBagConstraints gbcLabel = (GridBagConstraints) gbc.clone();
246        gbcLabel.insets = new Insets( 2, 2, 2, 0 );
247        gbcLabel.gridwidth = 1;
248        gbcLabel.weightx = 0;
249
250        if ( c == null )
251          c = new JLabel( "" );
252
253        gbl.setConstraints( l, gbcLabel );
254        target.add( l );
255        gbl.setConstraints( c, gbc );
256        target.add( c );
257    }
258
259    /// Sets up menu entries
260    private void setupMenu( boolean isApplet ) {
261        JMenu fileMenu = new JMenu( "File" );
262        JMenu optionMenu = new JMenu( "Option" );
263
264        fileMenu.add( new MenuItemV2( "Save Selected Options...", this ));
265        fileMenu.add( new MenuItemV2( "Load Options...", this ));
266        fileMenu.addSeparator();
267        fileMenu.add( new MenuItemV2( "Save as PNG...", this ));
268        fileMenu.add( new MenuItemV2( "Load PNG File to Compare...", this ));
269        fileMenu.add( new MenuItemV2( "Page Setup...", this ));
270        fileMenu.add( new MenuItemV2( "Print...", this ));
271        fileMenu.addSeparator();
272        if ( !isApplet )
273          fileMenu.add( new MenuItemV2( "Exit", this ));
274        else
275          fileMenu.add( new MenuItemV2( "Close", this ));
276
277        displayGridCBMI = new CheckboxMenuItemV2( "Display Grid", true, this );
278        force16ColsCBMI = new CheckboxMenuItemV2( "Force 16 Columns", false, this );
279        showFontInfoCBMI = new CheckboxMenuItemV2( "Display Font Info", false, this );
280        optionMenu.add( displayGridCBMI );
281        optionMenu.add( force16ColsCBMI );
282        optionMenu.add( showFontInfoCBMI );
283
284        JMenuBar mb = parent.getJMenuBar();
285        if ( mb == null )
286          mb = new JMenuBar();
287        mb.add( fileMenu );
288        mb.add( optionMenu );
289
290        parent.setJMenuBar( mb );
291
292        String fontList[] =
293          GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
294
295        for ( int i = 0; i < fontList.length; i++ )
296          fontMenu.addItem( fontList[i] );
297        fontMenu.setSelectedItem( "Dialog" );
298
299        styleMenu.addItem( "Plain" );
300        styleMenu.addItem( "Bold" );
301        styleMenu.addItem( "Italic" );
302        styleMenu.addItem( "Bold Italic" );
303
304        transformMenu.addItem( "None" );
305        transformMenu.addItem( "Scale" );
306        transformMenu.addItem( "Shear" );
307        transformMenu.addItem( "Rotate" );
308
309        transformMenuG2.addItem( "None" );
310        transformMenuG2.addItem( "Scale" );
311        transformMenuG2.addItem( "Shear" );
312        transformMenuG2.addItem( "Rotate" );
313
314        methodsMenu.addItem( "drawString" );
315        methodsMenu.addItem( "drawChars" );
316        methodsMenu.addItem( "drawBytes" );
317        methodsMenu.addItem( "drawGlyphVector" );
318        methodsMenu.addItem( "TextLayout.draw" );
319        methodsMenu.addItem( "GlyphVector.getOutline + draw" );
320        methodsMenu.addItem( "TextLayout.getOutline + draw" );
321
322        textMenu.addItem( "Unicode Range" );
323        textMenu.addItem( "All Glyphs" );
324        textMenu.addItem( "User Text" );
325        textMenu.addItem( "Text File" );
326        textMenu.addActionListener ( this ); // listener added later so unneeded events not thrown
327    }
328
329    /// Sets up the all dialogs used in Font2DTest...
330    private void setupDialog( boolean isApplet ) {
331        if (!isApplet)
332                filePromptDialog = new JFileChooser( );
333        else
334                filePromptDialog = null;
335
336        /// Prepare user text dialog...
337        userTextDialog = new JDialog( parent, "User Text", false );
338        JPanel dialogTopPanel = new JPanel();
339        JPanel dialogBottomPanel = new JPanel();
340        LabelV2 message1 = new LabelV2( "Enter text below and then press update" );
341        LabelV2 message2 = new LabelV2( "(Unicode char can be denoted by \\uXXXX)" );
342        LabelV2 message3 = new LabelV2( "(Supplementary chars can be denoted by \\UXXXXXX)" );
343        userTextArea = new JTextArea( "Font2DTest!" );
344        ButtonV2 bUpdate = new ButtonV2( "Update", this );
345        userTextArea.setFont( new Font( "dialog", Font.PLAIN, 12 ));
346        dialogTopPanel.setLayout( new GridLayout( 3, 1 ));
347        dialogTopPanel.add( message1 );
348        dialogTopPanel.add( message2 );
349        dialogTopPanel.add( message3 );
350        dialogBottomPanel.add( bUpdate );
351        //ABP
352        JScrollPane userTextAreaSP = new JScrollPane(userTextArea);
353        userTextAreaSP.setPreferredSize(new Dimension(300, 100));
354
355        userTextDialog.getContentPane().setLayout( new BorderLayout() );
356        userTextDialog.getContentPane().add( "North", dialogTopPanel );
357        userTextDialog.getContentPane().add( "Center", userTextAreaSP );
358        userTextDialog.getContentPane().add( "South", dialogBottomPanel );
359        userTextDialog.pack();
360        userTextDialog.addWindowListener( new WindowAdapter() {
361            public void windowClosing( WindowEvent e ) {
362                userTextDialog.hide();
363            }
364        });
365
366        /// Prepare printing dialog...
367        printCBGroup = new ButtonGroup();
368        printModeCBs[ fp.ONE_PAGE ] =
369          new JRadioButton( "Print one page from currently displayed character/line",
370                         true );
371        printModeCBs[ fp.CUR_RANGE ] =
372          new JRadioButton( "Print all characters in currently selected range",
373                         false );
374        printModeCBs[ fp.ALL_TEXT ] =
375          new JRadioButton( "Print all lines of text",
376                         false );
377        LabelV2 l =
378          new LabelV2( "Note: Page range in native \"Print\" dialog will not affect the result" );
379        JPanel buttonPanel = new JPanel();
380        printModeCBs[ fp.ALL_TEXT ].setEnabled( false );
381        buttonPanel.add( new ButtonV2( "Print", this ));
382        buttonPanel.add( new ButtonV2( "Cancel", this ));
383
384        printDialog = new JDialog( parent, "Print...", true );
385        printDialog.setResizable( false );
386        printDialog.addWindowListener( new WindowAdapter() {
387            public void windowClosing( WindowEvent e ) {
388                printDialog.hide();
389            }
390        });
391        printDialog.getContentPane().setLayout( new GridLayout( printModeCBs.length + 2, 1 ));
392        printDialog.getContentPane().add( l );
393        for ( int i = 0; i < printModeCBs.length; i++ ) {
394            printCBGroup.add( printModeCBs[i] );
395            printDialog.getContentPane().add( printModeCBs[i] );
396        }
397        printDialog.getContentPane().add( buttonPanel );
398        printDialog.pack();
399
400        /// Prepare font information dialog...
401        fontInfoDialog = new JDialog( parent, "Font info", false );
402        fontInfoDialog.setResizable( false );
403        fontInfoDialog.addWindowListener( new WindowAdapter() {
404            public void windowClosing( WindowEvent e ) {
405                fontInfoDialog.hide();
406                showFontInfoCBMI.setState( false );
407            }
408        });
409        JPanel fontInfoPanel = new JPanel();
410        fontInfoPanel.setLayout( new GridLayout( fontInfos.length, 1 ));
411        for ( int i = 0; i < fontInfos.length; i++ ) {
412            fontInfos[i] = new LabelV2("");
413            fontInfoPanel.add( fontInfos[i] );
414        }
415        fontInfoDialog.getContentPane().add( fontInfoPanel );
416
417        /// Move the location of the dialog...
418        userTextDialog.setLocation( 200, 300 );
419        fontInfoDialog.setLocation( 0, 400 );
420    }
421
422    /// RangeMenu object signals using this function
423    /// when Unicode range has been changed and text needs to be redrawn
424    public void fireRangeChanged() {
425        int range[] = rm.getSelectedRange();
426        fp.setTextToDraw( fp.RANGE_TEXT, range, null, null );
427        if(canDisplayCheck) {
428            setupFontList(range[0], range[1]);
429        }
430        if ( showFontInfoCBMI.getState() )
431          fireUpdateFontInfo();
432    }
433
434    /// Changes the message on the status bar
435    public void fireChangeStatus( String message, boolean error ) {
436        /// If this is not ran as an applet, use own status bar,
437        /// Otherwise, use the appletviewer/browser's status bar
438        statusBar.setText( message );
439        if ( error )
440          fp.showingError = true;
441        else
442          fp.showingError = false;
443    }
444
445    /// Updates the information about the selected font
446    public void fireUpdateFontInfo() {
447        if ( showFontInfoCBMI.getState() ) {
448            String infos[] = fp.getFontInfo();
449            for ( int i = 0; i < fontInfos.length; i++ )
450              fontInfos[i].setText( infos[i] );
451            fontInfoDialog.pack();
452        }
453    }
454
455    private void setupFontList(int rangeStart, int rangeEnd) {
456
457        int listCount = fontMenu.getItemCount();
458        int size = 16;
459
460        try {
461            size =  Float.valueOf(sizeField.getText()).intValue();
462        }
463        catch ( Exception e ) {
464            System.out.println("Invalid font size in the size textField. Using default value of 16");
465        }
466
467        int style = fontStyles[styleMenu.getSelectedIndex()];
468        Font f;
469        for (int i = 0; i < listCount; i++) {
470            String fontName = (String)fontMenu.getItemAt(i);
471            f = new Font(fontName, style, size);
472            if ((rm.getSelectedIndex() != RangeMenu.SURROGATES_AREA_INDEX) &&
473                canDisplayRange(f, rangeStart, rangeEnd)) {
474                fontMenu.setBit(i, true);
475            }
476            else {
477                fontMenu.setBit(i, false);
478            }
479        }
480
481        fontMenu.repaint();
482    }
483
484    protected boolean canDisplayRange(Font font, int rangeStart, int rangeEnd) {
485        for (int i = rangeStart; i < rangeEnd; i++) {
486            if (font.canDisplay(i)) {
487                return true;
488            }
489        }
490        return false;
491    }
492
493    /// Displays a file load/save dialog and returns the specified file
494    private String promptFile( boolean isSave, String initFileName ) {
495        int retVal;
496        String str;
497
498        /// ABP
499        if ( filePromptDialog == null)
500                return null;
501
502        if ( isSave ) {
503            filePromptDialog.setDialogType( JFileChooser.SAVE_DIALOG );
504            filePromptDialog.setDialogTitle( "Save..." );
505            str = "Save";
506
507
508        }
509        else {
510            filePromptDialog.setDialogType( JFileChooser.OPEN_DIALOG );
511            filePromptDialog.setDialogTitle( "Load..." );
512            str = "Load";
513        }
514
515        if (initFileName != null)
516                filePromptDialog.setSelectedFile( new File( initFileName ) );
517        retVal = filePromptDialog.showDialog( this, str );
518
519        if ( retVal == JFileChooser.APPROVE_OPTION ) {
520                File file = filePromptDialog.getSelectedFile();
521                String fileName = file.getAbsolutePath();
522                if ( fileName != null ) {
523                        return fileName;
524                }
525        }
526
527        return null;
528    }
529
530    /// Converts user text into arrays of String, delimited at newline character
531    /// Also replaces any valid escape sequence with appropriate unicode character
532    /// Support \\UXXXXXX notation for surrogates
533    private String[] parseUserText( String orig ) {
534        int length = orig.length();
535        StringTokenizer perLine = new StringTokenizer( orig, "\n" );
536        String textLines[] = new String[ perLine.countTokens() ];
537        int lineNumber = 0;
538
539        while ( perLine.hasMoreElements() ) {
540            StringBuffer converted = new StringBuffer();
541            String oneLine = perLine.nextToken();
542            int lineLength = oneLine.length();
543            int prevEscapeEnd = 0;
544            int nextEscape = -1;
545            do {
546                int nextBMPEscape = oneLine.indexOf( "\\u", prevEscapeEnd );
547                int nextSupEscape = oneLine.indexOf( "\\U", prevEscapeEnd );
548                nextEscape = (nextBMPEscape < 0)
549                    ? ((nextSupEscape < 0)
550                       ? -1
551                       : nextSupEscape)
552                    : ((nextSupEscape < 0)
553                       ? nextBMPEscape
554                       : Math.min(nextBMPEscape, nextSupEscape));
555
556                if ( nextEscape != -1 ) {
557                    if ( prevEscapeEnd < nextEscape )
558                        converted.append( oneLine.substring( prevEscapeEnd, nextEscape ));
559
560                    prevEscapeEnd = nextEscape + (nextEscape == nextBMPEscape ? 6 : 8);
561                    try {
562                        String hex = oneLine.substring( nextEscape + 2, prevEscapeEnd );
563                        if (nextEscape == nextBMPEscape) {
564                            converted.append( (char) Integer.parseInt( hex, 16 ));
565                        } else {
566                            converted.append( new String( Character.toChars( Integer.parseInt( hex, 16 ))));
567                        }
568                    }
569                    catch ( Exception e ) {
570                        int copyLimit = Math.min(lineLength, prevEscapeEnd);
571                        converted.append( oneLine.substring( nextEscape, copyLimit ));
572                    }
573                }
574            } while (nextEscape != -1);
575            if ( prevEscapeEnd < lineLength )
576              converted.append( oneLine.substring( prevEscapeEnd, lineLength ));
577            textLines[ lineNumber++ ] = converted.toString();
578        }
579        return textLines;
580    }
581
582    /// Reads the text from specified file, detecting UTF-16 encoding
583    /// Then breaks the text into String array, delimited at every line break
584    private void readTextFile( String fileName ) {
585        try {
586            String fileText, textLines[];
587            BufferedInputStream bis =
588              new BufferedInputStream( new FileInputStream( fileName ));
589            int numBytes = bis.available();
590            if (numBytes == 0) {
591                throw new Exception("Text file " + fileName + " is empty");
592            }
593            byte byteData[] = new byte[ numBytes ];
594            bis.read( byteData, 0, numBytes );
595            bis.close();
596
597            /// If byte mark is found, then use UTF-16 encoding to convert bytes...
598            if (numBytes >= 2 &&
599                (( byteData[0] == (byte) 0xFF && byteData[1] == (byte) 0xFE ) ||
600                 ( byteData[0] == (byte) 0xFE && byteData[1] == (byte) 0xFF )))
601              fileText = new String( byteData, "UTF-16" );
602            /// Otherwise, use system default encoding
603            else
604              fileText = new String( byteData );
605
606            int length = fileText.length();
607            StringTokenizer perLine = new StringTokenizer( fileText, "\n" );
608            /// Determine "Return Char" used in this file
609            /// This simply finds first occurrence of CR, CR+LF or LF...
610            for ( int i = 0; i < length; i++ ) {
611                char iTh = fileText.charAt( i );
612                if ( iTh == '\r' ) {
613                    if ( i < length - 1 && fileText.charAt( i + 1 ) == '\n' )
614                      perLine = new StringTokenizer( fileText, "\r\n" );
615                    else
616                      perLine = new StringTokenizer( fileText, "\r" );
617                    break;
618                }
619                else if ( iTh == '\n' )
620                  /// Use the one already created
621                  break;
622            }
623            int lineNumber = 0, numLines = perLine.countTokens();
624            textLines = new String[ numLines ];
625
626            while ( perLine.hasMoreElements() ) {
627                String oneLine = perLine.nextToken();
628                if ( oneLine == null )
629                  /// To make LineBreakMeasurer to return a valid TextLayout
630                  /// on an empty line, simply feed it a space char...
631                  oneLine = " ";
632                textLines[ lineNumber++ ] = oneLine;
633            }
634            fp.setTextToDraw( fp.FILE_TEXT, null, null, textLines );
635            rm.setEnabled( false );
636            methodsMenu.setEnabled( false );
637        }
638        catch ( Exception ex ) {
639            fireChangeStatus( "ERROR: Failed to Read Text File; See Stack Trace", true );
640            ex.printStackTrace();
641        }
642    }
643
644    /// Returns a String storing current configuration
645    private void writeCurrentOptions( String fileName ) {
646        try {
647            String curOptions = fp.getCurrentOptions();
648            BufferedOutputStream bos =
649              new BufferedOutputStream( new FileOutputStream( fileName ));
650            /// Prepend title and the option that is only obtainable here
651            int range[] = rm.getSelectedRange();
652            String completeOptions =
653              ( "Font2DTest Option File\n" +
654                displayGridCBMI.getState() + "\n" +
655                force16ColsCBMI.getState() + "\n" +
656                showFontInfoCBMI.getState() + "\n" +
657                rm.getSelectedItem() + "\n" +
658                range[0] + "\n" + range[1] + "\n" + curOptions + tFileName);
659            byte toBeWritten[] = completeOptions.getBytes( "UTF-16" );
660            bos.write( toBeWritten, 0, toBeWritten.length );
661            bos.close();
662        }
663        catch ( Exception ex ) {
664            fireChangeStatus( "ERROR: Failed to Save Options File; See Stack Trace", true );
665            ex.printStackTrace();
666        }
667    }
668
669    /// Updates GUI visibility/status after some parameters have changed
670    private void updateGUI() {
671        int selectedText = textMenu.getSelectedIndex();
672
673        /// Set the visibility of User Text dialog
674        if ( selectedText == fp.USER_TEXT )
675          userTextDialog.show();
676        else
677          userTextDialog.hide();
678        /// Change the visibility/status/availability of Print JDialog buttons
679        printModeCBs[ fp.ONE_PAGE ].setSelected( true );
680        if ( selectedText == fp.FILE_TEXT || selectedText == fp.USER_TEXT ) {
681            /// ABP
682            /// update methodsMenu to show that TextLayout.draw is being used
683            /// when we are in FILE_TEXT mode
684            if ( selectedText == fp.FILE_TEXT )
685                methodsMenu.setSelectedItem("TextLayout.draw");
686            methodsMenu.setEnabled( selectedText == fp.USER_TEXT );
687            printModeCBs[ fp.CUR_RANGE ].setEnabled( false );
688            printModeCBs[ fp.ALL_TEXT ].setEnabled( true );
689        }
690        else {
691            /// ABP
692            /// update methodsMenu to show that drawGlyph is being used
693            /// when we are in ALL_GLYPHS mode
694            if ( selectedText == fp.ALL_GLYPHS )
695                methodsMenu.setSelectedItem("drawGlyphVector");
696            methodsMenu.setEnabled( selectedText == fp.RANGE_TEXT );
697            printModeCBs[ fp.CUR_RANGE ].setEnabled( true );
698            printModeCBs[ fp.ALL_TEXT ].setEnabled( false );
699        }
700        /// Modify RangeMenu and fontInfo label availabilty
701        if ( selectedText == fp.RANGE_TEXT ) {
702            fontInfos[1].setVisible( true );
703            rm.setEnabled( true );
704        }
705        else {
706            fontInfos[1].setVisible( false );
707            rm.setEnabled( false );
708        }
709    }
710
711    /// Loads saved options and applies them
712    private void loadOptions( String fileName ) {
713        try {
714            BufferedInputStream bis =
715              new BufferedInputStream( new FileInputStream( fileName ));
716            int numBytes = bis.available();
717            byte byteData[] = new byte[ numBytes ];
718            bis.read( byteData, 0, numBytes );
719            bis.close();
720            if ( numBytes < 2 ||
721                (byteData[0] != (byte) 0xFE || byteData[1] != (byte) 0xFF) )
722              throw new Exception( "Not a Font2DTest options file" );
723
724            String options = new String( byteData, "UTF-16" );
725            StringTokenizer perLine = new StringTokenizer( options, "\n" );
726            String title = perLine.nextToken();
727            if ( !title.equals( "Font2DTest Option File" ))
728              throw new Exception( "Not a Font2DTest options file" );
729
730            /// Parse all options
731            boolean displayGridOpt = Boolean.parseBoolean( perLine.nextToken() );
732            boolean force16ColsOpt = Boolean.parseBoolean( perLine.nextToken() );
733            boolean showFontInfoOpt = Boolean.parseBoolean( perLine.nextToken() );
734            String rangeNameOpt = perLine.nextToken();
735            int rangeStartOpt = Integer.parseInt( perLine.nextToken() );
736            int rangeEndOpt = Integer.parseInt( perLine.nextToken() );
737            String fontNameOpt = perLine.nextToken();
738            float fontSizeOpt = Float.parseFloat( perLine.nextToken() );
739            int fontStyleOpt = Integer.parseInt( perLine.nextToken() );
740            int fontTransformOpt = Integer.parseInt( perLine.nextToken() );
741            int g2TransformOpt = Integer.parseInt( perLine.nextToken() );
742            int textToUseOpt = Integer.parseInt( perLine.nextToken() );
743            int drawMethodOpt = Integer.parseInt( perLine.nextToken() );
744            int antialiasOpt = Integer.parseInt(perLine.nextToken());
745            int fractionalOpt = Integer.parseInt(perLine.nextToken());
746            int lcdContrast = Integer.parseInt(perLine.nextToken());
747            String userTextOpt[] = { "Font2DTest!" };
748            String dialogEntry = "Font2DTest!";
749            if (textToUseOpt == fp.USER_TEXT )  {
750                int numLines = perLine.countTokens(), lineNumber = 0;
751                if ( numLines != 0 ) {
752                    userTextOpt = new String[ numLines ];
753                    dialogEntry = "";
754                    for ( ; perLine.hasMoreElements(); lineNumber++ ) {
755                        userTextOpt[ lineNumber ] = perLine.nextToken();
756                        dialogEntry += userTextOpt[ lineNumber ] + "\n";
757                    }
758                }
759            }
760
761            /// Reset GUIs
762            displayGridCBMI.setState( displayGridOpt );
763            force16ColsCBMI.setState( force16ColsOpt );
764            showFontInfoCBMI.setState( showFontInfoOpt );
765            rm.setSelectedRange( rangeNameOpt, rangeStartOpt, rangeEndOpt );
766            fontMenu.setSelectedItem( fontNameOpt );
767            sizeField.setText( String.valueOf( fontSizeOpt ));
768            styleMenu.setSelectedIndex( fontStyleOpt );
769            transformMenu.setSelectedIndex( fontTransformOpt );
770            transformMenuG2.setSelectedIndex( g2TransformOpt );
771            textMenu.setSelectedIndex( textToUseOpt );
772            methodsMenu.setSelectedIndex( drawMethodOpt );
773            antiAliasMenu.setSelectedIndex( antialiasOpt );
774            fracMetricsMenu.setSelectedIndex( fractionalOpt );
775            contrastSlider.setValue(lcdContrast);
776
777            userTextArea.setText( dialogEntry );
778            updateGUI();
779
780            if ( textToUseOpt == fp.FILE_TEXT ) {
781              tFileName = perLine.nextToken();
782              readTextFile(tFileName );
783            }
784
785            /// Reset option variables and repaint
786            fp.loadOptions( displayGridOpt, force16ColsOpt,
787                            rangeStartOpt, rangeEndOpt,
788                            fontNameOpt, fontSizeOpt,
789                            fontStyleOpt, fontTransformOpt, g2TransformOpt,
790                            textToUseOpt, drawMethodOpt,
791                            antialiasOpt, fractionalOpt,
792                            lcdContrast, userTextOpt );
793            if ( showFontInfoOpt ) {
794                fireUpdateFontInfo();
795                fontInfoDialog.show();
796            }
797            else
798              fontInfoDialog.hide();
799        }
800        catch ( Exception ex ) {
801            fireChangeStatus( "ERROR: Failed to Load Options File; See Stack Trace", true );
802            ex.printStackTrace();
803        }
804    }
805
806    /// Loads a previously saved image
807    private void loadComparisonPNG( String fileName ) {
808        try {
809            BufferedImage image =
810                javax.imageio.ImageIO.read(new File(fileName));
811            JFrame f = new JFrame( "Comparison PNG" );
812            ImagePanel ip = new ImagePanel( image );
813            f.setResizable( false );
814            f.getContentPane().add( ip );
815            f.addWindowListener( new WindowAdapter() {
816                public void windowClosing( WindowEvent e ) {
817                    ( (JFrame) e.getSource() ).dispose();
818                }
819            });
820            f.pack();
821            f.show();
822        }
823        catch ( Exception ex ) {
824            fireChangeStatus( "ERROR: Failed to Load PNG File; See Stack Trace", true );
825            ex.printStackTrace();
826        }
827    }
828
829    /// Interface functions...
830
831    /// ActionListener interface function
832    /// Responds to JMenuItem, JTextField and JButton actions
833    public void actionPerformed( ActionEvent e ) {
834        Object source = e.getSource();
835
836        if ( source instanceof JMenuItem ) {
837            JMenuItem mi = (JMenuItem) source;
838            String itemName = mi.getText();
839
840            if ( itemName.equals( "Save Selected Options..." )) {
841                String fileName = promptFile( true, "options.txt" );
842                if ( fileName != null )
843                  writeCurrentOptions( fileName );
844            }
845            else if ( itemName.equals( "Load Options..." )) {
846                String fileName = promptFile( false, "options.txt" );
847                if ( fileName != null )
848                  loadOptions( fileName );
849            }
850            else if ( itemName.equals( "Save as PNG..." )) {
851                String fileName = promptFile( true, fontMenu.getSelectedItem() + ".png" );
852                if ( fileName != null )
853                  fp.doSavePNG( fileName );
854            }
855            else if ( itemName.equals( "Load PNG File to Compare..." )) {
856                String fileName = promptFile( false, null );
857                if ( fileName != null )
858                  loadComparisonPNG( fileName );
859            }
860            else if ( itemName.equals( "Page Setup..." ))
861              fp.doPageSetup();
862            else if ( itemName.equals( "Print..." ))
863              printDialog.show();
864            else if ( itemName.equals( "Close" ))
865              parent.dispose();
866            else if ( itemName.equals( "Exit" ))
867              System.exit(0);
868        }
869
870        else if ( source instanceof JTextField ) {
871            JTextField tf = (JTextField) source;
872            float sz = 12f;
873            try {
874                 sz = Float.parseFloat(sizeField.getText());
875                 if (sz < 1f || sz > 120f) {
876                      sz = 12f;
877                      sizeField.setText("12");
878                 }
879            } catch (Exception se) {
880                 sizeField.setText("12");
881            }
882            if ( tf == sizeField )
883              fp.setFontParams( fontMenu.getSelectedItem(),
884                                sz,
885                                styleMenu.getSelectedIndex(),
886                                transformMenu.getSelectedIndex() );
887        }
888
889        else if ( source instanceof JButton ) {
890            String itemName = ( (JButton) source ).getText();
891            /// Print dialog buttons...
892            if ( itemName.equals( "Print" )) {
893                for ( int i = 0; i < printModeCBs.length; i++ )
894                  if ( printModeCBs[i].isSelected() ) {
895                      printDialog.hide();
896                      fp.doPrint( i );
897                  }
898            }
899            else if ( itemName.equals( "Cancel" ))
900              printDialog.hide();
901            /// Update button from Usert Text JDialog...
902            else if ( itemName.equals( "Update" ))
903              fp.setTextToDraw( fp.USER_TEXT, null,
904                                parseUserText( userTextArea.getText() ), null );
905        }
906        else if ( source instanceof JComboBox ) {
907            JComboBox c = (JComboBox) source;
908
909            /// RangeMenu handles actions by itself and then calls fireRangeChanged,
910            /// so it is not listed or handled here
911            if ( c == fontMenu || c == styleMenu || c == transformMenu ) {
912                float sz = 12f;
913                try {
914                    sz = Float.parseFloat(sizeField.getText());
915                    if (sz < 1f || sz > 120f) {
916                        sz = 12f;
917                        sizeField.setText("12");
918                    }
919                } catch (Exception se) {
920                    sizeField.setText("12");
921                }
922                fp.setFontParams(fontMenu.getSelectedItem(),
923                                 sz,
924                                 styleMenu.getSelectedIndex(),
925                                 transformMenu.getSelectedIndex());
926            } else if ( c == methodsMenu )
927              fp.setDrawMethod( methodsMenu.getSelectedIndex() );
928            else if ( c == textMenu ) {
929
930                if(canDisplayCheck) {
931                    fireRangeChanged();
932                }
933
934                int selected = textMenu.getSelectedIndex();
935
936                if ( selected == fp.RANGE_TEXT )
937                  fp.setTextToDraw( fp.RANGE_TEXT, rm.getSelectedRange(),
938                                    null, null );
939                else if ( selected == fp.USER_TEXT )
940                  fp.setTextToDraw( fp.USER_TEXT, null,
941                                    parseUserText( userTextArea.getText() ), null );
942                else if ( selected == fp.FILE_TEXT ) {
943                    String fileName = promptFile( false, null );
944                    if ( fileName != null ) {
945                      tFileName = fileName;
946                      readTextFile( fileName );
947                    } else {
948                        /// User cancelled selection; reset to previous choice
949                        c.setSelectedIndex( currentTextChoice );
950                        return;
951                    }
952                }
953                else if ( selected == fp.ALL_GLYPHS )
954                  fp.setTextToDraw( fp.ALL_GLYPHS, null, null, null );
955
956                updateGUI();
957                currentTextChoice = selected;
958            }
959            else if ( c == transformMenuG2 ) {
960                fp.setTransformG2( transformMenuG2.getSelectedIndex() );
961            }
962            else if (c == antiAliasMenu || c == fracMetricsMenu) {
963                if (c == antiAliasMenu) {
964                    boolean enabled = FontPanel.AAValues.
965                        isLCDMode(antiAliasMenu.getSelectedItem());
966                        contrastSlider.setEnabled(enabled);
967                }
968                fp.setRenderingHints(antiAliasMenu.getSelectedItem(),
969                                     fracMetricsMenu.getSelectedItem(),
970                                     contrastSlider.getValue());
971            }
972        }
973    }
974
975    public void stateChanged(ChangeEvent e) {
976         Object source = e.getSource();
977         if (source instanceof JSlider) {
978             fp.setRenderingHints(antiAliasMenu.getSelectedItem(),
979                                  fracMetricsMenu.getSelectedItem(),
980                                  contrastSlider.getValue());
981         }
982    }
983
984    /// ItemListener interface function
985    /// Responds to JCheckBoxMenuItem, JComboBox and JCheckBox actions
986    public void itemStateChanged( ItemEvent e ) {
987        Object source = e.getSource();
988
989        if ( source instanceof JCheckBoxMenuItem ) {
990            JCheckBoxMenuItem cbmi = (JCheckBoxMenuItem) source;
991            if ( cbmi == displayGridCBMI )
992              fp.setGridDisplay( displayGridCBMI.getState() );
993            else if ( cbmi == force16ColsCBMI )
994              fp.setForce16Columns( force16ColsCBMI.getState() );
995            else if ( cbmi == showFontInfoCBMI ) {
996                if ( showFontInfoCBMI.getState() ) {
997                    fireUpdateFontInfo();
998                    fontInfoDialog.show();
999                }
1000                else
1001                  fontInfoDialog.hide();
1002            }
1003        }
1004    }
1005
1006    private static void printUsage() {
1007        String usage = "Usage: java -jar Font2DTest.jar [options]\n" +
1008            "\nwhere options include:\n" +
1009            "    -dcdc | -disablecandisplaycheck disable canDisplay check for font\n" +
1010            "    -?    | -help                   print this help message\n" +
1011            "\nExample :\n" +
1012            "     To disable canDisplay check on font for ranges\n" +
1013            "     java -jar Font2DTest.jar -dcdc";
1014        System.out.println(usage);
1015        System.exit(0);
1016    }
1017
1018    /// Main function
1019    public static void main(String argv[]) {
1020
1021        if(argv.length > 0) {
1022            if(argv[0].equalsIgnoreCase("-disablecandisplaycheck") ||
1023               argv[0].equalsIgnoreCase("-dcdc")) {
1024                canDisplayCheck = false;
1025            }
1026            else {
1027                printUsage();
1028            }
1029        }
1030
1031        UIManager.put("swing.boldMetal", Boolean.FALSE);
1032        final JFrame f = new JFrame( "Font2DTest" );
1033        final Font2DTest f2dt = new Font2DTest( f, false );
1034        f.addWindowListener( new WindowAdapter() {
1035            public void windowOpening( WindowEvent e ) { f2dt.repaint(); }
1036            public void windowClosing( WindowEvent e ) { System.exit(0); }
1037        });
1038
1039        f.getContentPane().add( f2dt );
1040        f.pack();
1041        f.show();
1042    }
1043
1044    /// Inner class definitions...
1045
1046    /// Class to display just an image file
1047    /// Used to show the comparison PNG image
1048    private final class ImagePanel extends JPanel {
1049        private final BufferedImage bi;
1050
1051        public ImagePanel( BufferedImage image ) {
1052            bi = image;
1053        }
1054
1055        public Dimension getPreferredSize() {
1056            return new Dimension( bi.getWidth(), bi.getHeight() );
1057        }
1058
1059        public void paintComponent( Graphics g ) {
1060            g.drawImage( bi, 0, 0, this );
1061        }
1062    }
1063
1064    /// Classes made to avoid repetitive calls... (being lazy)
1065    private final class ButtonV2 extends JButton {
1066        public ButtonV2( String name, ActionListener al ) {
1067            super( name );
1068            this.addActionListener( al );
1069        }
1070    }
1071
1072    private final class ChoiceV2 extends JComboBox {
1073
1074        private BitSet bitSet = null;
1075
1076        public ChoiceV2() {;}
1077
1078        public ChoiceV2( ActionListener al ) {
1079            super();
1080            this.addActionListener( al );
1081        }
1082
1083        public ChoiceV2( ActionListener al, boolean fontChoice) {
1084            this(al);
1085            if(fontChoice) {
1086                //Register this component in ToolTipManager
1087                setToolTipText("");
1088                bitSet = new BitSet();
1089                setRenderer(new ChoiceV2Renderer(this));
1090            }
1091        }
1092
1093        public String getToolTipText() {
1094            int index = this.getSelectedIndex();
1095            String fontName = (String) this.getSelectedItem();
1096            if(fontName != null &&
1097               (textMenu.getSelectedIndex() == fp.RANGE_TEXT)) {
1098                if (getBit(index)) {
1099                    return "Font \"" + fontName + "\" can display some characters in \"" +
1100                        rm.getSelectedItem() + "\" range";
1101                }
1102                else {
1103                    return "Font \"" + fontName + "\" cannot display any characters in \"" +
1104                        rm.getSelectedItem() + "\" range";
1105                }
1106            }
1107            return super.getToolTipText();
1108        }
1109
1110        public void setBit(int bitIndex, boolean value) {
1111            bitSet.set(bitIndex, value);
1112        }
1113
1114        public boolean getBit(int bitIndex) {
1115            return bitSet.get(bitIndex);
1116        }
1117    }
1118
1119    private final class ChoiceV2Renderer extends DefaultListCellRenderer {
1120
1121        private ImageIcon yesImage, blankImage;
1122        private ChoiceV2 choice = null;
1123
1124        public ChoiceV2Renderer(ChoiceV2 choice) {
1125            BufferedImage yes =
1126                new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
1127            Graphics2D g = yes.createGraphics();
1128            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1129                               RenderingHints.VALUE_ANTIALIAS_ON);
1130            g.setColor(Color.BLUE);
1131            g.drawLine(0, 5, 3, 10);
1132            g.drawLine(1, 5, 4, 10);
1133            g.drawLine(3, 10, 10, 0);
1134            g.drawLine(4, 9, 9, 0);
1135            g.dispose();
1136            BufferedImage blank =
1137                new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
1138            yesImage = new ImageIcon(yes);
1139            blankImage = new ImageIcon(blank);
1140            this.choice = choice;
1141        }
1142
1143        public Component getListCellRendererComponent(JList list,
1144                                                      Object value,
1145                                                      int index,
1146                                                      boolean isSelected,
1147                                                      boolean cellHasFocus) {
1148
1149            if(textMenu.getSelectedIndex() == fp.RANGE_TEXT) {
1150
1151                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1152
1153                //For JComboBox if index is -1, its rendering the selected index.
1154                if(index == -1) {
1155                    index = choice.getSelectedIndex();
1156                }
1157
1158                if(choice.getBit(index)) {
1159                    setIcon(yesImage);
1160                }
1161                else {
1162                    setIcon(blankImage);
1163                }
1164
1165            } else {
1166                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1167                setIcon(blankImage);
1168            }
1169
1170            return this;
1171        }
1172    }
1173
1174    private final class LabelV2 extends JLabel {
1175        public LabelV2( String name ) {
1176            super( name );
1177        }
1178    }
1179
1180    private final class MenuItemV2 extends JMenuItem {
1181        public MenuItemV2( String name, ActionListener al ) {
1182            super( name );
1183            this.addActionListener( al );
1184        }
1185    }
1186
1187    private final class CheckboxMenuItemV2 extends JCheckBoxMenuItem {
1188        public CheckboxMenuItemV2( String name, boolean b, ItemListener il ) {
1189            super( name, b );
1190            this.addItemListener( il );
1191        }
1192    }
1193}
1194